Skip to content

Problem with implementing trait for async fns #47

Closed
@WaffleLapkin

Description

@WaffleLapkin

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions