feat(tonic): rpit future for generated server traits #2283
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation
Probably the main motivation here is that when I use vs code to auto-generate fn's for a tonic stub, it spews out the guts of async_trait. It's educational, but it's quite heavy and unnecessary since Rust 1.75. I'm pretty confident that
Future + Send
is about as strict as tonic intends to be toward generated service stubs, but of course let's chat about it.Secondarily, I count microseconds at both my day job and my home hobby work. I also spend a lot of time stepping in a debugger. Box types are pretty annoying to deal with in the vs code debugger, and they do tend to cost a little time. I use
fn f() -> impl Future<Output = _> + Send
in all my async traits nowadays. It works great at home and at work.Solution
Supported at 1.75, which is tonic's current MSRV, return position impl trait (RPIT) makes user code cleaner, friendlier in a debugger, and possibly a couple nanoseconds faster due to fewer boxed Future indirections. There is still one Box Future at the tower_service interface due to the switch from RPIT style "simple" traits and the fancier static associated type Future style from tower.
This unwraps 2 layers of Box indirection into static types: 1 in the framework's generated GRPC code below tower, and 1 in user code specified by async_trait.
It's similar to #1697 but instead I went for the full change, to evaluate the expected impact more fully.
Impact
In the expected use cases where people generate their code, they would remove
#[tonic::async_trait]
from their implementations of Server stubs. You can see in all the tests and examples, that's the only typical user-facing delta.In the use cases where people write their own non-generated Server implementations, they would need to move the trait impl's Future type into the call function's return type. If they're reimplementing UnaryService, they would possibly need to explicitly narrow any wrapped tower_service::Future to Send as the UnaryService (and friends) now explicitly does.
It is a breaking change, but it's a simplification that seemed worthwhile to me with the burden of 1-line deletion per service. The vs code auto-generated todo!() implementations are a little nicer this way too, which is a quality of life boost for me :-)
Relates to: #1651
UX before:
UX after:
Testing
Due to the nature of the change, a wide range of tests exercise it. It is a change to the existing infrastructure rather than a new feature, so there's no duplication or parameterization of tests that would increase coverage. The tests were quite reassuring, in that they only required the removal of
#[tonic::async_trait]
, which was by design.