Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem with implementing trait for async fns #47

Closed
WaffleLapkin opened this issue Nov 2, 2019 · 7 comments
Closed

Problem with implementing trait for async fns #47

WaffleLapkin opened this issue Nov 2, 2019 · 7 comments

Comments

@WaffleLapkin
Copy link

I'm trying to implement trait with async fn for all async fns. 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:

  1. Why the first implementation doesn't work, but the second does?
  2. Is it possible to remove async move {} from the last example? (Box::pin(self()) leads to the same error the parameter type Fut may not live long enough)
  3. Is it possible to write implementation like this, but without so much boilerplate?
  4. Can async_trait to accept implementations like in the first example? (after some changes in how macro works)
@dtolnay
Copy link
Owner

dtolnay commented Nov 2, 2019

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
    }
}

@WaffleLapkin
Copy link
Author

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
    }
}

@WaffleLapkin
Copy link
Author

I've just found out that in my code this doesn't work :(

Because

  1. I have param in both Trait and F
  2. 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`

example on playground

However, unboxed Fn trait works (example on playground)

See also rust-lang/rust#48869 and rust-lang/rust#50238

@dtolnay
Copy link
Owner

dtolnay commented Nov 2, 2019

= note: downstream crates may implement trait `StableFn<_>` for type `Run`

That's so crazy. :(

@jasta
Copy link

jasta commented Mar 24, 2022

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: rust-lang/rust#95182 as this is what's required to make it work without traits despite bizarre rustc error messages.

@dtolnay
Copy link
Owner

dtolnay commented Nov 26, 2022

Closing as this is not going to be actionable in async-trait.

@dtolnay dtolnay closed this as completed Nov 26, 2022
@samvrlewis
Copy link

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.

Playground link of your code modified to compile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants