dtolnay / async-trait

Type erasure for async trait methods
Apache License 2.0
1.81k stars 84 forks source link

Issue with reference lifetimes in closures #257

Open tezlm opened 8 months ago

tezlm commented 8 months ago

On latest async-trait (0.1.77) this code doesn't compile:

struct Foo;

#[async_trait::async_trait]
trait Bar {
    async fn foo(&self, call: &(dyn (Fn(&String) -> bool) + Sync));
}

#[async_trait::async_trait]
impl Bar for Foo {
    async fn foo(&self, call: &(dyn (Fn(&String) -> bool) + Sync)) {
        let thing = "a".to_string();
        call(&thing);
    }
}

fn main() {
    Foo::foo(&Foo, &|a| a == "a");
}

This expands to:

    fn foo<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        call: &'life1 (dyn (Fn(&'life2 String) -> bool) + Sync),
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<Output = ()> + ::core::marker::Send + 'async_trait,
        >,
    >
    where
        'life0: 'async_trait,
        'life1: 'async_trait,
        'life2: 'async_trait,
        Self: 'async_trait;

And gives the compiler error:

error[E0597]: `thing` does not live long enough
  --> src/main.rs:12:14
   |
10 |     async fn foo(&self, call: &(dyn (Fn(&String) -> bool) + Sync)) {
   |                         ---- lifetime `'1` appears in the type of `call`
11 |         let thing = "a".to_string();
   |             ----- binding `thing` declared here
12 |         call(&thing);
   |         -----^^^^^^-
   |         |    |
   |         |    borrowed value does not live long enough
   |         argument requires that `thing` is borrowed for `'1`
13 |     }
   |     - `thing` dropped here while still borrowed

Manually removing 'life2 makes the code compile.

dtolnay commented 6 months ago

This can be worked around by writing the higher-rank trait bound for the function type, rather than relying on lifetime elision.

#[async_trait::async_trait]
trait Bar {
    async fn foo(&self, call: &(dyn (for<'a> Fn(&'a String) -> bool) + Sync));
                                     ^^^^^^^     ^^
}

#[async_trait::async_trait]
impl Bar for Foo {
    async fn foo(&self, call: &(dyn (for<'a> Fn(&'a String) -> bool) + Sync)) {
                                     ^^^^^^^     ^^
        let thing = "a".to_string();
        call(&thing);
    }
}