async-trait
async-trait copied to clipboard
Problem with implementing trait for `async fn`s
I'm trying to implement trait with async fn
for all async fn
s. Simplified example:
#[async_trait]
trait Trait {
async fn run(&self);
}
#[async_trait]
impl<F, Fut> Trait for F
where
F: Fn() -> Fut + Sync,
Fut: Future<Output = ()> + Send,
{
async fn run(&self) {
self().await
}
}
simplified `cargo expand`
trait Trait {
fn run<'s, 'async_trait>(&'s self) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where
's: 'async_trait,
Self: 'async_trait;
}
impl<F, Fut> Trait for F
where
F: Fn() -> Fut + Sync,
Fut: Future<Output = ()> + Send,
{
fn run<'s, 'async_trait>(&'s self) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where
's: 'async_trait,
Self: 'async_trait,
{
#[allow(clippy::used_underscore_binding)]
async fn __run<F, Fut>(_self: &F)
where
F: Fn() -> Fut + Sync,
Fut: Future<Output = ()> + Send,
{
_self().await
}
Box::pin(__run::<F, Fut>(self))
}
}
But sadly, this doesn't work:
error[E0309]: the parameter type `Fut` may not live long enough
--> src/lib.rs:16:1
|
16 | #[async_trait]
| ^^^^^^^^^^^^^^
17 | impl<F, Fut> Trait for F
| --- help: consider adding an explicit lifetime bound `Fut: 'async_trait`...
|
note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds
--> src/lib.rs:16:1
|
16 | #[async_trait]
| ^^^^^^^^^^^^^^
error: aborting due to previous error
(adding Fut: 'async_trait
is impossible because it leads to impl has stricter requirements than trait
errors)
But with by-hand desugaring this implementation is possible:
impl<F, Fut> Trait for F
where
F: Fn() -> Fut + Sync,
Fut: Future<Output = ()> + Send,
{
fn run<'s, 'async_trait>(&'s self) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where
's: 'async_trait,
Self: 'async_trait,
{
Box::pin(async move { self().await })
}
}
#[test]
fn test() {
let closure = || async { () };
Trait::run(&closure);
}
So, my questions are:
- Why the first implementation doesn't work, but the second does?
- Is it possible to remove
async move {}
from the last example? (Box::pin(self())
leads to the same errorthe parameter type
Futmay not live long enough
) - Is it possible to write implementation like this, but without so much boilerplate?
- Can
async_trait
to accept implementations like in the first example? (after some changes in how macro works)
This is a compiler limitation with how Output
associated types of Fn
-family traits are specified. One way to work around this it is to avoid Fn
by using an equivalent blanket implemented trait that avoids the special Fn
trait syntax.
use async_trait::async_trait;
use std::future::Future;
trait AnyFn {
type Output;
fn call(&self) -> Self::Output;
}
impl<F, T> AnyFn for F
where
F: Fn() -> T,
{
type Output = T;
fn call(&self) -> Self::Output {
self()
}
}
#[async_trait]
trait Trait {
async fn run(&self);
}
#[async_trait]
impl<F> Trait for F
where
F: AnyFn + Sync,
F::Output: Future<Output = ()> + Send,
{
async fn run(&self) {
self.call().await
}
}
Thanks!
It's also worth noticing that this can be done throw #![feature(unboxed_closures)]
and Fn<()>
:
#![feature(unboxed_closures)]
#[async_trait]
trait Trait {
async fn run(&self);
}
#[async_trait]
impl<F> Trait for F
where
F: Fn<()> + Sync,
F::Output: Future<Output = ()> + Send,
{
async fn run(&self) {
self().await
}
}
I've just found out that in my code this doesn't work :(
Because
- I have param in both
Trait
andF
- I have a struct that also implements
Trait
So my code produce the following error:
error[E0119]: conflicting implementations of trait `Trait<_>` for type `Run`:
--> src/lib.rs:34:1
|
22 | / impl<A, F> Trait<A> for F
23 | | where
24 | | F: StableFn<A>,
25 | | F::Output: Future<Output = ()>,
... |
29 | | }
30 | | }
| |_- first implementation here
...
34 | impl<A> Trait<A> for Run {
| ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Run`
|
= note: downstream crates may implement trait `StableFn<_>` for type `Run`
However, unboxed Fn
trait works (example on playground)
See also https://github.com/rust-lang/rust/issues/48869 and https://github.com/rust-lang/rust/issues/50238
= note: downstream crates may implement trait `StableFn<_>` for type `Run`
That's so crazy. :(
I'm having trouble making this solution work when using a borrowed argument as with:
[#async_trait]
trait Hello {
async fn say_hi(&self, to_whom: &'_ str);
}
It seems no matter what I try I run into lifetime issues associated with to_whom that I can't satisfy. This appears to be due to the fact that I'm needing to specify a constraint that to_whom
must be bound to lifetime of the the returned Future but there's no syntax I can find to achieve this. See my playground attempt here:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e77117e78782e90097105a9a943188f7
This rustc issue I filed may also be relevant: https://github.com/rust-lang/rust/issues/95182 as this is what's required to make it work without traits despite bizarre rustc error messages.
Closing as this is not going to be actionable in async-trait.
I'm having trouble making this solution work when using a borrowed argument as with:
[#async_trait] trait Hello { async fn say_hi(&self, to_whom: &'_ str); }
@jasta, it's more than a year later but I was also having trouble with this. It seems like it's possible if you make the AnyFn
trait generic over the lifetime so you can ensure the lifetime of the input parameter and the lifetime of the resulting future the same.