Goal:
trait MyTrait {
async fn foo(&self) -> i32;
}
impl MyTrait for MyType {
async fn foo(&self) -> i32 {
/* ... * /
}
}
Several of these problems apply equally well to impl Trait in traits.
An async fn returns an impl Future
. In a trait, this would be desugared to anonymous associated type:
trait MyTrait {
type Future: Future<Output = i32>;
fn foo(&self) -> Self::Future;
}
However, they capture all input lifetimes, therefore the associated type needs to be generic over lifetimes:
trait MyTrait {
type Future<'a>: Future<Output = i32>;
fn foo<'a>(&'a self) -> Self::Future<'a>;
}
Design work on GATs is complete, but they are not implemented. First, the chalk trait implementation needs to be integrated into rustc. Work on that is underway.
The returned future of the async function will implement Future
, but what about other traits? By virtue of the design of async functions, in the concrete case it will "leak" auto traits like Send
and Sync
. But in the generic case, its not obvious how to require that it implement Send
or Sync
. This comes up in two cases:
- How can I define a trait so that an async method's future must be
Send
orSync
? - How can I take a
T: Trait
and require that its async method's future must beSend
orSync
?
For impl Trait, the proposed solution is for impl Trait to be disallowed in trait definitions, and instead only allowed in implementations. The trait definitions would use an "explicit" form. Applied to async fn, the original example would look like this:
trait MyTrait {
type Future<'a>: Future<Output = i32>;
fn foo<'a>(&'a self) -> Self::Future<'a>;
}
impl MyTrait for MyType {
abstract type Future<'a>; // The type definition of the future assoc type should
// be inferred, because we said it was 'abstract'
// This matches the signature.
async fn foo(&self) -> i32 {
/* ... * /
}
}
Users can then require Send
using bounds on the associated type, either in the trait definition or in a bound on a generic parameter (e.g. T: MyTrait, T::Future: Send
).
While this seems like a possibly workable solution for impl Trait, something about it is very unsatisfactory for async fn
because of the way it spills the guts of the feature it is trying to hide.
An alternative design would be to find a way to write bounds on the future returned by an async fn directly, while preserving async fn syntax. Hypothetical examples:
trait MyTrait {
#[async_bounds(Send)]
async fn foo(&self) -> i32;
}
fn bar<T: MyTrait>(x: T) where
async T::foo: Send,
{ /* ... */ }
(This is just one syntax, others are certainly plausible).
Consider the Service
trait:
trait Service {
type Request;
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;
fn call(&mut self, req: Self::Request) -> Self::Future;
}
It might be nice to transform this to an async fn:
trait Service {
type Request;
type Response;
type Error;
async fn call(&mut self, req: Self::Request) -> Result<Self::Response, Self::Error>;
}
However, this changes the meaning, because the returned future now captures the lifetime of &mut self
.
As it stands, the Self
type is used as a factory to initialize futures, but does not contain state that is used by those futures during their execution.
Ultimately, this pattern is not well supported by async fn in traits. There is no definition that supports both patterns, because they have different implications: in the "initializer" pattern, the state is no longer held while the future is executing, whereas in the pattern encoded in async fn, it is. This means ultimately any trait has to choose between these two options, and there isn't a trait definition that is more flexible.
For this reason, this doesn't seem like a "problem": traits that act as "future factories," rather than stateful objects with asynchronous methods, will need to use the associated type (or possible impl Future
) syntax, not async fn.
Can traits with anonymous associated types/
async
methods be object safe?