---
tags: draft-rfc
---
# AFIT Send Bound RFC Draft
- Feature Name: `async_trait_method_bounds`
- Start Date: 2023-02-16
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
# Summary
[summary]: #summary
Provides a lightweight way to add bounds to the futures returned by async methods when referring to traits.
We expect this to be primarily used to require `Send` futures on async methods when needed.
# Motivation
[motivation]: #motivation
[RFC 3185] adds support for `async fn` in static traits, and the implementation is now at a point where users can experiment with the feature on nightly Rust.
One known limitation is that there is no way to add additional bounds to the future returned by `async` methods.
In particular, this prevents spawning on a multithreaded executor from generic code, a limitation that was described in [MVP feature announcement blog post][announcement-post].
The following example program, introduced in [Niko Matsakis's post on the issue][niko-afit-send-bound-1], cannot compile in current nightly Rust.
```rust
trait HealthCheck {
async fn check(&mut self, server: &Server) -> bool;
}
fn start_health_check<H>(health_check: H, server: Server)
where
H: HealthCheck + Send + 'static,
{
tokio::spawn(async move {
while health_check.check(&server).await {
tokio::time::sleep(Duration::from_secs(1)).await;
}
emit_failure_log(&server).await;
});
}
```
The problem is that `start_health_check` has no way of knowing whether the future returned from `health_check.check(...)` is `Send`.
Although this scenario is somewhat specific, early experience with async functions in traits suggests that many real-world codebases will run into this issue and need a solution.
This RFC describes new syntax and associated semantics to concisely add the necessary `Send` bound to make this example compile and run.
[RFC 3185]: https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html
[announcement-post]: https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html#limitation-spawning-from-generics
[niko-afit-send-bound-1]: https://smallcultfollowing.com/babysteps/blog/2023/02/01/async-trait-send-bounds-part-1-intro/#why-do-we-care-about-send-bounds
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
Sometimes when using async functions in traits, it is necessary to add additional bounds to the future returned by the function.
One common case is when you need to rely on a future being `Send` within a generic context.
Consider the following example.
```rust=
trait async HealthCheck {
async fn check(&mut self, server: &Server) -> bool;
}
fn start_health_check<H>(health_check: H, server: Server)
where
H: async HealthCheck + Send + 'static,
{
tokio::spawn(async move {
while health_check.check(&server).await {
tokio::time::sleep(Duration::from_secs(1)).await;
}
emit_failure_log(&server).await;
});
}
```
Here all we know about the `check` method is that it returns a future that resolves to a `bool`.
The function signature on line 2 and the trait bound on `H` on line 7 do not say anything about whether that future is `Send`, so the compiler cannot assume that it is.
This means, as written, that this program would return an error saying that the async block passed to `tokio::spawn` is not `Send`.
We can fix this by making a small change to line 7:
```rust
H: async(Send) HealthCheck + Send + 'static
```
The whole example would now look like this:
```rust=
trait async HealthCheck {
async fn check(&mut self, server: &Server) -> bool;
}
fn start_health_check<H>(health_check: H, server: Server)
where
H: async(Send) HealthCheck + Send + 'static,
{
tokio::spawn(async move {
while health_check.check(&server).await {
tokio::time::sleep(Duration::from_secs(1)).await;
}
emit_failure_log(&server).await;
});
}
```
The `async(Send)` modifier in the bound means that any of the async functions in `HealthCheck` will be required to return futures that are additionally `Send`.
This means that the compiler is now able to conclude that the whole async block passed to `tokio::spawn` is `Send` and the code now can compile successfully.
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
## Syntax Changes
This proposal includes two syntax additions.
The first is to add an optional `async` keyword in trait definitions, so traits can either be declared as `trait Foo { ... }` or `trait async Foo { ... }`.
The send is to add a new `async` qualifier to trait references in bounds that can occur as `async` or `async(Bound)`.
The figure below summarizes this change as a diff to an idealized version of the current trait bound grammar.
```diff
<bound> ::= <lifetime>
| <ident> <trait-args>
| "for" <lifetime-list> <bound>
| <bound> + <bound>
+ | <async-qualifier> <bound>
+
+ <async-qualifier> ::= "async"
+ | "async" "(" <bound> ")"
```
## Semantics
Corresponding to the syntax changes, there are two ways in which this proposal changes the semantics of Rust.
The `trait async Foo { .. }` part is straightforward.
It just means that the compiler would allow async functions in the trait definition.
Without the `async` declaration here, the compiler would continue to emit an error in these cases.
The new bounds syntax is a little more in depth.
For any trait that includes `async` methods, we would require any references to it to have the `async` keyword.
In other words, `fn foo<T: async HealthCheck>() { ... }` is okay while `fn foo<T: HealthCheck>() { ... }` is an error.
This `async` keyword can optionally be following by an additional bound.
This will almost always be an auto trait like `Send` or `Sync` but the grammar allows additional bounds such as lifetimes or other traits.
When this bound is present, the compiler would add this bound to the hidden `impl Future` type for any `async fn`s in the trait.
## Worked Example
To illustrate how this feature works, we will look at our original example and show how it could be "desugared" by the compiler.
Recall our example program:
```rust=
trait async HealthCheck {
async fn check(&mut self, server: &Server) -> bool;
}
fn start_health_check<H>(health_check: H, server: Server)
where
H: async(Send) HealthCheck + Send + 'static,
{
tokio::spawn(async move {
while health_check.check(&server).await {
tokio::time::sleep(Duration::from_secs(1)).await;
}
emit_failure_log(&server).await;
});
}
```
First, let's consider the `async HealthCheck` trait definition.
Async methods desugar into a function that returns `impl Future`, and in traits this is done by a hidden generic associated type.
Thus, to the compiler the trait looks more like this:
```rust
trait HealthCheck {
type OpaqueCheck<'a>: Future<Output = bool> + 'a;
fn check<'a>(&'a mut self, server: &'a Server) -> Self::OpaqueCheck;
}
```
Now, looking at the `start_health_check` function, the compiler would treat it as if it were declared as follows:
```rust
fn start_health_check<H>(health_check: H, server: Server)
where
H: HealthCheck + Send + 'static,
<H as HealthCheck>::OpaqueCheck: Send,
{ ... }
```
We could also imagine adding additional bounds, like this:
```rust
fn start_health_check<H>(health_check: H, server: Server)
where
H: async(Send + Sync + 'static) HealthCheck + Send + 'static,
{ ... }
```
In this case, the compiler would treat these similarly:
```rust
fn start_health_check<H>(health_check: H, server: Server)
where
H: HealthCheck + Send + 'static,
<H as HealthCheck>::OpaqueCheck: Send + Sync + 'static,
{ ... }
```
Note that the bounds from `async(Bounds)` would only apply to functions declared in the trait as `async`.
These would not apply to other functions or associated types.
# Drawbacks
[drawbacks]: #drawbacks
This change adds additional syntax and associated semantics which makes Rust more complex.
Of note is that this additional complexity is added to the trait system which is already one of the more complex parts of the language.
To mitigate this, we have tried to make it so this feature can be explained concisely and adds the minimal amount of complexity to still be useful in a variety of cases.
The `async(Send)` bounds are somewhat coarse grained.
We could imagine a trait that has many `async` functions in it but the user only cares if one or two return `Send` futures.
This feature does not have the precision needed to accurately express this.
We expect that these cases will be rare and that `async(Send)` will be sufficient in the vast majority of cases.
We can address the cases where more precision is needed with future features such as [Return Type Notation][return-type-notation].
# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
In wg-async, we have discussed a number of possible ways to address the issue of needing to add `Send` bounds for return types in async functions in traits.
We believe the proposal here strikes a good balance between complexity and usefulness.
We have focused on adding bounds at the use site (`start_health_check` in our example) rather than at the trait definition, which would require all implementors of the trait to provide `Send` futures.
We believe that adding the bounds at the trait definition will lead to an ecosystem where the majority of traits require `Send`, which would impose unnecessary requirements on single threaded and embedded async runtimes.
The async `Send` bound problem needs to be solved, either using this or another solution.
Every real-world codebase we've seen that's tried to use the nightly support for async functions in traits has run into this issue.
The strengths are this solution are that it's a fairly small addition to the language that gives a relatively easy to use solution to this problem.
There is no way to solve this problem without language changes.
The reason is that async functions in traits have a hidden opaque type as their return type and there is currently no way to refer to it to add additional bounds when needed.
This proposal does make Rust more complex, but it also provides a solution for a problem that programmers will encounter.
The missing `async(Send)` bound can easily be suggested in error messages, aiding its learnability and discoverability.
The following subsections discuss some specific alternatives and other questions.
## Why `trait async Foo` and not `async trait Foo`?
While `async trait Foo` reads more naturally, we are proposing `trait async Foo` for the following reasons:
1. Symmetry between the trait declaration and the bounds. An async trait will always be referred to as `async Trait`. In this way, one can think of `async` as part of the trait's name.
2. Consistency with the [Keyword Generics][keyword-generics] initiative.
## Return Type Notation
[return-type-notation]: #return-type-notation
[Return Type Notation][niko-rtn] (RTN) is a more precise and more general approach to solve this problem. We see that proposal as complementary to this one and would like to see both added to the language. The strengths of RTN are that it allows more precise bounds, including adding bounds on some but not all functions, and that it generalizes more nicely to `-> impl Trait` methods.
The downside is that it effectively requires the user to duplicate the definition of the trait anywhere `Send` bounds are needed.
Sometimes that precision will be needed, but the lighter weight syntax here will be easier to use and should meet the needs of the vast majority of use cases.
[niko-rtn]: https://smallcultfollowing.com/babysteps/blog/2023/02/13/return-type-notation-send-bounds-part-2/
# Prior art
[prior-art]: #prior-art
Currently the [async-trait] crate is widely used to work around the lack of async functions in traits in Rust.
It makes futures `Send`-by-default with optional syntax to opt out.
The opt-out decision is made on a per-trait rather than per-method basis, which serves as evidence that adding bounds to all methods or none is likely to work well.
The async-trait crate places bounds at the trait definition and implementation rather than at the use site like this proposal does.
[async-trait]: https://docs.rs/async-trait/latest/async_trait/
# Unresolved questions
[unresolved-questions]: #unresolved-questions
## Interaction with RPITIT
Async functions in traits are closely related to the [return position impl trait in traits (RPITIT)][rpitit] feature that is also under development.
While the details are not completely worked out, it is likely that RPITIT will allow some mixing and matching between `async fn` and `-> impl Future<Output = T> + '_`.
The question is how `async(Send)` would interact with `-> impl Future` functions?
We do not think this needs to be solved before merging this RFC because there is not an RFC for RPITIT yet.
That said, we want to make sure an acceptable solution exists.
We suggest that `async(Bounds)` should only apply to methods declared as `async` in the trait definition.
This means to bounds would also apply when using an impl that uses the desugared `-> impl Future`, since the method was declared as `async` in the trait.
If the function was declared in the trait as `-> impl Future`, we do not recommend doing any kind of pattern matching to recognize it could be "resugared" as an async function.
Return type notation could be used to refer to `impl Trait` methods.
This does leave a hazard if the trait definition changes from using `async fn` to `-> impl Future`.
One possible workaround could be to add something like an `#[async]` attribute that instructs the compiler to treat the function as if it were declared as `async` when processing `async(Bounds)`.
[rpitit]: https://rust-lang.github.io/impl-trait-initiative/RFCs/rpit-in-traits.html
# Future possibilities
[future-possibilities]: #future-possibilities
> Think about what the natural extension and evolution of your proposal would
> be and how it would affect the language and project as a whole in a holistic
> way. Try to use this section as a tool to more fully consider all possible
> interactions with the project and language in your proposal.
> Also consider how this all fits into the roadmap for the project
> and of the relevant sub-team.
>
> This is also a good place to "dump ideas", if they are out of scope for the
> RFC you are writing but otherwise related.
>
> If you have tried and cannot think of any future possibilities,
> you may simply state that you cannot think of anything.
>
> Note that having something written down in the future-possibilities section
> is not a reason to accept the current or a future RFC; such notes should be
> in the section on motivation or rationale in this or subsequent RFCs.
> The section merely provides additional information.
## Keyword Generics
[keyword-generics]: #keyword-generics
This proposal was developed in collaboration with the [keyword generics initiative][kgi] so that the syntax introduced here would be compatible with and generalize nicely to keyword generics.
The [recent status update] provides examples of a new maybe-async syntax, written `?async`.
That would be allowed in any of the new places `async` is introduced in this proposal.
The two also work well together in allowing `?async(Send)`, where the added bounds would be ignored in non-async contexts and added in async contexts.
[kgi]: https://github.com/rust-lang/keyword-generics-initiative
[recent status update]: https://blog.rust-lang.org/inside-rust/2023/02/23/keyword-generics-progress-report-feb-2023.html
## `async(Send) fn foo() { ... }`
While we have focused on adding `Send` bounds at the use site, there may be times where it's desirable to add `Send` bounds on the trait definition.
This would require that all implementors of the trait provide `Send` futures.
Alternatively, a particular impl might want to promise that its returned futures will always be `Send`.
The `async(Send)` syntax could readily extend to these cases.
A few examples are given below:
```rust
// A trait where all its async methods return `Send` futures
trait async(Send) AlwaysSend {
async fn foo(&self);
async fn bar(&self);
}
// A trait where just one method is required to be send
trait async SometimesSend {
async fn foo(&self);
async(Send) fn bar(&self);
}
// an impl that adds an additional `Send` bound to all futures
impl async(Send) MyTrait for () {
async fn foo(&self);
async fn bar(&self);
}
// an impl that adds a `Send` bound to just one method
impl async MyTrait for () {
async(Send) fn foo(&self);
async fn bar(&self);
}
// a standalone function that promises to always return a `Send` future
async(Send) fn foo() { ... }
```
Note that these extensions are out of scope for this RFC and could be finalized later if desired.