Lifetime may not live long enough in async closure?

I have the following code:

use std::future::Future;

pub trait AsyncFnOnce<Arg>: FnOnce(Arg) -> <Self as AsyncFnOnce<Arg>>::Fut {
    type Fut: Future<Output = <Self as AsyncFnOnce<Arg>>::Output>;
    type Output;
}
impl<Arg, Func: FnOnce(Arg) -> Fut, Fut: Future> AsyncFnOnce<Arg> for Func {
    type Fut = Fut;
    type Output = Fut::Output;
}

// --------------------------------------------------------------

async fn baz(cb: impl for<'buf> AsyncFnOnce<&'buf mut Vec<u8>>) {
    let mut buf = vec![];
    cb(&mut buf).await;
    println!("{buf:?}");
}

#[tokio::main]
async fn main() {
    baz(|s: &mut Vec<u8>| async move {
        s.push(1);
    });
}

Error:

error: lifetime may not live long enough
  --> src/main.rs:22:27
   |
22 |       baz(|s: &mut Vec<u8>| async move {
   |  _____________-___________-_^
   | |             |           |
   | |             |           return type of closure `[async block@src/main.rs:22:27: 24:6]` contains a lifetime `'2`
   | |             let's call the lifetime of this reference `'1`
23 | |         s.push(1);
24 | |     });
   | |_____^ returning this value requires that `'1` must outlive `'2

How can I solve it ?

Edit: I still don't know why rust is complaining about. The error message is not very helpful.

Duplicate of:

The custom AsyncFnOnce only works for funtion items. The closure types are not supported as a limit from rustc. Lifetime bounds to use for Future that isn't supposed to outlive calling scope - #4 by steffahn

#[tokio::main]
async fn main() {
    baz(f);
}
// this works
async fn f(s: &mut Vec<u8>) {
    s.push(1);
}
3 Likes

Is there any workaround ? I know this work in today's stable rust:

Playground

use std::{
    future::{poll_fn, Future},
    pin::pin,
    task::{Context, Poll},
};

async fn baz(
    mut poll: impl for<'cx, 'c, 'buf> FnMut(&'cx mut Context<'c>, &'buf mut Vec<u8>) -> Poll<()>,
) {
    let mut buf = vec![];
    poll_fn(|cx| poll(cx, &mut buf)).await;
    println!("{buf:?}");
}

async fn qux(fut: impl Future<Output = u8>) {
    let mut fut = pin!(fut);
    baz(move |cx, s: &mut Vec<u8>| {
        fut.as_mut().poll(cx).map(|val| {
            s.push(val);
        })
    })
    .await
}

Cool, that's a nice solution.


Niko wrote some blog posts about async closures several months ago explaining the hassles and potential solutions:

I happened to write a nightly-only macro https://docs.rs/async_closure like the solution mentioned in the first post link.

Using the macro and its companion trait, your full code can be written as

#![feature(async_fn_in_trait)]
use async_closure::{async_owned_closure_once as cb, capture_no_lifetimes::AsyncFnOnce};

#[tokio::main]
async fn main() {
    baz(
        cb!({/* capture variables */}; async |s: &mut Vec<u8>| -> () {
            s.push(1);
        }),
    )
    .await;
}

async fn baz(cb: impl for<'buf> AsyncFnOnce<(&'buf mut Vec<u8>,), Output = ()>) {
    let mut buf = vec![];
    cb.call_once((&mut buf,)).await; // due to the mock trait, no calling sugar here
    println!("{buf:?}");
}

The macro is expanded as

{
    struct __AsyncClosure {}
    impl ::async_closure::capture_no_lifetimes::AsyncFnOnce<(&mut Vec<u8>,)> for __AsyncClosure {
        type Output = ();
        async fn call_once(self, __args: (&mut Vec<u8>,)) -> () {
            let Self { .. } = self;
            #[allow(unused_parens)]
            let (s,) = __args;
            {
                s.push(1);
            }
        }
    }
    #[allow(clippy::redundant_field_names)] __AsyncClosure {}
}

But I don't recommend using it though.

2 Likes

Return a BoxFuture and use something such as

to properly nudge the necessary lifetime quantification

    baz(hrtb!(|s: &mut Vec<u8>| -> BoxFuture<'_, ()> { async move {
        s.push(1);
    }.boxed() });

Although rather than using hrtb!, you could also just define a baz_boxed helper function:

async fn baz_boxed(cb: impl FnOnce(&mut Vec<u8>) -> BoxFuture<'_, ()>)) {
    baz(cb).await
}

baz_boxed(|s: &mut Vec<u8>| async move {
    s.push(1);
}.boxed())

(In fact, what hrtb! does is doing that under the hood for you.)

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.