tonic
tonic copied to clipboard
Drop async_trait
Motivation
This change was born mostly out of curiosity rather than a serious attempt to get it in any time soon. The main motivation of course is to get rid of the async_trait macro and boxed futures in favor of recently stabilized async fn in traits.
Solution
Remove usage of the async_trait macro and adjust the server codegen parts to return desugared impl Future<…> + Send because of the Send bound problem.
Caveats
This requires a nightly compiler but with that passes all tests as well as some manual tests I performed.
The larger question to answer though is how this would be merged into tonic eventually. tonic-health and tonic-reflection rely on generated code which prohibits a gradual, feature-based integration. Moreover, this would require quite a large bump of MSRV to 1.75.
@matze Nice! I think we could potentially offer this as an opt-in during codegen? I don't think there is anything stopping us from support both since its mostly codegen and user callsite code that is affected. What do you think?
~~If I understand correctly and in order to have mid-term compatibility means adding a separate flag async_trait that is automatically enabled with codegen?~~ Even easier, if codegen is on but async_trait not, then this one here could kick in. Should indeed make the PR much smaller as well and be green. Let me try :-)
@LucioFranco I tested but the initial caveat still holds, all crates using pre-generated services require either async_trait or a nightly compiler. And emitting code with the feature flag check in place does not work either.
A similar PR is approved for axum, how do you think just breaking as well?
@LucioFranco I tested but the initial caveat still holds, all crates using pre-generated services require either async_trait or a nightly compiler. And emitting code with the feature flag check in place does not work either.
I am not following what you mean by this?
A similar https://github.com/tokio-rs/axum/pull/2308 is approved for axum, how do you think just breaking as well?
I think we should if we can get it so it works for both and users can choose.
I am not following what you mean by this?
I mean I don't see how this could ever work for both. tonic-health and tonic-reflection contain pre-generated implementations that either fit a hypothetical feature flag that represents async-trait-less tonic or not. I mean we could go back to the times when tonic-health and tonic-reflection were generated at build time but I guess you had good reasons to not do so.
Firstly let me say that I don't want to come across as prescriptive. Like many others, I'd like to see async_trait deprecated because it was a (very good) workaround for a shortcoming in Rust itself. Now that that shortcoming has been addressed, the continued use of async_trait comes with a trade-off between not breaking users and performance. I love and appreciate that @LucioFranco wants to bend over backwards to not break users. Too many hours have been wasted by less conscientious open source maintainers who don't mind breaking their users.
However, async_trait must eventually go away. The crux of the issue is where the burden of responsibility falls. Do you break your users in the name of performance? Do you maintain feature flag gymnastics to support both? If so, for how long?
In my view, tonic should draw a line in the sand, beyond which async_trait will be dropped.
I'd like to see tonic stamp out a new major version (the fabled 1.0, perhaps?) that drops async_trait, while studiously maintaining an older branch and version still using async_trait, until some cut-off date. In the interim, the deprecation of the older version should be communicated as widely as possible. Let everyone know, async_trait is going away, but you can upgrade at your convenience.
This is a lot of work for the tonic maintainers. This is potentially a lot of work for tonic users. But, I think it balances the trade off without unduly burdening anyone.
- Tonic won't suddenly externalize the burden of dropping
async_traitand break users. - Users will get time to upgrade.
- All dependents (and there are a lot) are freed from needing to depend on
async_traitas well.
Ultimately I think the constraints here are:
- MSRV: we want to support a version of tonic that doesn't need GAT until we decide we no longer want to support the old compiler versions
- Minimize maintenance overhead
At the same time, I do wonder what the motivation is to get rid of async-trait? Have you measured a large effect on performance in your workloads? Or a large effect on compile-time due to the extra macros/dependency?
If axum is any indication, latency and throughput improved measurably but not with "large effect". For me personally this is of lower priority, not having to deal with generated, intransparent code is more important especially if there are alternatives out-of-the-box.
Obviously, we would like to drop it eventually, but the current state of async traits is not in its final state and it has a few issues that are left to be resolved before I would even consider it something to widely support in a library like this. Unless someone can put forth some valid data showing that there is a legit perf improvement to dropping async-trait then I don't think its worth investing on this right now versus other streams of work for tonic.