My following problem might be the same as: Lifetime may not live long enough for an async closure.
However, I'd like to present a boiled-down example and ask a few questions in that matter.
Consider the following code:
use std::future::Future;
async fn call_closure<C, Fut>(mut closure: C)
where
C: FnMut(&str) -> Fut, // `&str` needs to outlive `Fut`, but how to say?
Fut: Future<Output = ()>,
{
let s = String::from("Hello World!");
closure(&s).await;
}
#[tokio::main]
async fn main() {
call_closure(|arg| async move {
println!("{arg}");
})
.await;
}
Errors:
Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
--> src/main.rs:14:24
|
14 | call_closure(|arg| async move {
| ___________________----_^
| | | |
| | | return type of closure `impl Future<Output = [async output]>` contains a lifetime `'2`
| | has type `&'1 str`
15 | | println!("{arg}");
16 | | })
| |_____^ returning this value requires that `'1` must outlive `'2`
error: could not compile `playground` due to previous error
There seems to be a workaround:
use std::future::Future;
+use std::pin::Pin;
-async fn call_closure<C, Fut>(mut closure: C)
+async fn call_closure<C>(mut closure: C)
where
- C: FnMut(&str) -> Fut, // `&str` needs to outlive `Fut`, but how to say?
- Fut: Future<Output = ()>,
+ C: for<'a> FnMut(&'a str) -> Pin<Box<dyn 'a + Future<Output = ()>>>,
{
let s = String::from("Hello World!");
closure(&s).await;
}
#[tokio::main]
async fn main() {
- call_closure(|arg| async move {
- println!("{arg}");
+ call_closure(|arg| {
+ Box::pin(async move {
+ println!("{arg}");
+ })
})
.await;
}
My problems with this solution:
- The syntax is kinda bloated. Particularly, I will have to add a
Box::pin(async move {…})
to each invocation of the function taking the closure as argument. - I assume the heap allocation is unnecessary?
- In real-world code (other than this short example), things get complex quickly and I run into error messages during compilation which are difficult to understand (and which might even involve compiler errors as I have been getting something like "
expected X, found X
", which I can't reproduce now, though).
My question:
Is there any progress (or existing alternative) on how to solve this problem in a clean way?
Closures are a core-feature of Rust that I often use. And since I started doing a lot of asynchronous programming, I run into problems again and again because Rust doesn't let me express lifetimes properly in an easy way. And when I use workarounds (such as the workaround above), I often end up with complex constructs and sometimes even weird compiler errors.
All I would like to do is express lifetime relationships between arguments and the returned future of a closure. What can I do? Maybe there's some trick to use a type constructor or something? Or a feature gate that I can use?