MVP: Static async fn in traits
This section defines an initial minimum viable product (MVP). This MVP is meant to be a subset of async fns in traits that can be implemented and stabilized quickly.
In a nutshell
- In traits,
async fn foo(&self)
desugars to- an anonymous associated type
type Foo<'me>: Future<Output = ()>
(as this type is anonymous, users cannot actually name it; the nameFoo
here is for demonstrative purposes only) - a function
fn foo(&self) -> Self::Foo<'_>
that returns this future
- an anonymous associated type
- In impls,
async fn foo(&self)
desugars to- a value for the anonymous associated type
type Foo<'me> = impl Future<Output = ()>
- a function
fn foo(&self) -> Self::Foo<'_> { async move { ... } }
- a value for the anonymous associated type
- If the trait used
async fn
, then the impl must useasync fn
(and vice versa) - Traits that use
async fn
are not dyn safe- In the MVP, traits using async fn can only be used with
impl Trait
or generics
- In the MVP, traits using async fn can only be used with
What this enables
- The MVP is sufficient for projects like embassy, which already model async fns in traits in this way.
- TODO: Once we have a list of stakeholders, try to get a sense for how many uses of
async-trait
could be replaced
Notable limitations and workarounds
- No support for
dyn
- This is a fundamental limitation; the only workaround is to use
async-trait
- This is a fundamental limitation; the only workaround is to use
- No ability to name the resulting futures:
- This means that one cannot build non-generic adapters that reference those futures.
- Workaround: define a function alongside the impl and use a TAIT for its return type
- No ability to bound the resulting futures (e.g., to require that they are
Send
)- This rules out certain use cases when using work-stealing executor styles, such as the background logging scenario. Note that many other uses of async fn in traits will likely work fine even with a work-stealing executor: the only limitation is that one cannot write generic code that invokes
spawn
. - Workaround: do the desugaring manually when required, which would give a name for the relevant future.
- This rules out certain use cases when using work-stealing executor styles, such as the background logging scenario. Note that many other uses of async fn in traits will likely work fine even with a work-stealing executor: the only limitation is that one cannot write generic code that invokes
Implementation plan
- This MVP relies on having generic associated types and type alias impl trait, but they are making good progress.
- Otherwise, the implementation is a straightforward desugaring, similar to how inherent async fns are implemented
- We may wish to also ship a variant of the
async-trait
macro that lets people easily experiment with this feature
Forward compatibility
The MVP sidesteps a number of the more challenging design problems. It should be forwards compatible with:
- Adding support for
dyn
traits later - Adding a mechanism to bound the resulting futures
- WARNING: It is NOT compatible with Implied Send, however!
- Adding a mechanism to name the resulting futures
- The futures added here are anonymous, and we can always add explicit names later.
- If we were to name the resulting futures after the methods, and users had existing traits that used those same names already, this could present a conflict, but one that could be resolved.
- Supporting and bounding async drop
- This trait will not exist yet with the MVP, and supporting
async fn
doesn't enable anything fundamental that we don't have to solve anyway.
- This trait will not exist yet with the MVP, and supporting