In the following code snippet, a wrapper
function takes a callback, executes it and returns its result. This works well in sync code, but breaks in async code.
Sync version (works):
use std::future::Future;
fn wrapper<T, R>(
mut value: T,
f: impl for<'a> FnOnce(&'a mut T) -> R,
) -> R
{
let result = f(&mut value);
result
}
async fn func(a: &str) -> String {
wrapper(a, |a|
a.to_string()
)
}
Async Version (fails):
use std::future::Future;
async fn wrapper<T, R, F>(
mut value: T,
f: impl for<'a> FnOnce(&'a mut T) -> F,
) -> R
where
F: Future<Output = R>,
{
let result = f(&mut value).await;
result
}
async fn func(a: &str) -> String {
wrapper(a, |a| async move {
a.to_string()
}).await
}
Error:
error: lifetime may not live long enough
--> src/lib.rs:15:20
|
15 | wrapper(a, |a| async move {
| _________________--_^
| | ||
| | |return type of closure `[async block@src/lib.rs:15:20: 17:6]` contains a lifetime `'2`
| | has type `&'1 mut &str`
16 | | a.to_string()
17 | | }).await
| |_____^ returning this value requires that `'1` must outlive `'2`
error: could not compile `playground` (lib) due to previous error
It seems that the borrow checker doesn't allow the async move
closure to capture the a
reference, even though the closure execution finishes executing before wrapper.await
returns and wrapper.await
is happening fully within the lifetime of a
, therefore the a
reference will outlive the lifetime of the closure. What is going on here? And is there a way to fix it?
Note: I'm aware that it would work if I put the a.to_string()
call outside of the async move
closure and only capture the String
by value, but that's not the goal. This is a toy example and in my real world example, there are several await calls happening within the closure and I can't move that line out of the closure. Also, to_string
isn't actually to_string
, but a function with side effects that cannot be reordered.