Is there a workaround for creating an async closure that holds a reference over an await point?
use std::time::Duration;
use tokio::time::sleep;
fn main() {
let closure = |v: &u64| async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
};
}
Fails with:
error: lifetime may not live long enough
--> src/main.rs:5:29
|
5 | let closure = |v: &u64| async move {
| _______________________-___-_^
| | | |
| | | return type of closure `[async block@src/main.rs:5:29: 8:6]` contains a lifetime `'2`
| | let's call the lifetime of this reference `'1`
6 | | sleep(Duration::from_secs(1)).await;
7 | | println!("{}", v);
8 | | };
| |_____^ returning this value requires that `'1` must outlive `'2`
I'm aware that async closures could help. This works:
#![feature(async_closure)]
use std::time::Duration;
use tokio::time::sleep;
fn main() {
let closure = async move |v: &u64| {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
};
}
but given that they're not stable yet, I was wondering if there is any other workaround to make this work.
The problem is that the compiler infers the closure to accept &'some_lifetime u64
instead of &'any_lifetime u64
as it would do for functions.
Usually, when this is a problem we can pass the closure into a function that takes it and returns it but constrain the closure to be for<'a> Fn(&'a u64)
(or just Fn(&u64)
), and this helps the compiler infer the right lifetime. But here we cannot do that, because such function will disallow the returned future to borrow from the parameter, as I explained in Calling a generic async function with a (mutably) borrowed argument.
If you can change the closure to a function, this is the simplest solution.
Otherwise, if the closure captures, you can box the returned future and then use the aforementioned function:
use std::future::Future;
use std::pin::Pin;
fn force_hrtb<F: Fn(&u64) -> Pin<Box<dyn Future<Output = ()> + '_>>>(f: F) -> F {
f
}
let closure = force_hrtb(|v: &u64| {
Box::pin(async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
})
});
If the cost of boxing and dynamic dispatch is unacceptable, but you can use nightly, you can use the unstable feature closure_lifetime_binder
to force the compiler to treate &u64
as &'any_lifetime u64
. Unfortunately, because closure_lifetime_binder
requires the return type to be written explicitly, we also need to do that and we can do that only with another unstable feature, type_alias_impl_trait
:
#![feature(closure_lifetime_binder, type_alias_impl_trait)]
use std::future::Future;
type ClosureRet<'a> = impl Future<Output = ()> + 'a;
let closure = for<'a> |v: &'a u64| -> ClosureRet<'a> {
async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
}
};