Description
I tried this code:
struct A;
struct B<'b>(&'b mut A);
async fn m(_b: &mut B<'_>) {}
async fn tx<
'f,
'b: 'f,
'a: 'b,
F: std::future::Future<Output=()> + 'f,
N: FnOnce(&'b mut B<'a>) -> F,
>(a: &'a mut A, n: N) {
let mut b = B(a);
n(&mut b).await;
}
#[tokio::main]
pub async fn main() {
tx(
&mut A,
|b| async { m(b).await; },
).await;
}
I expected to see this happen: Compile fine.
Instead, this happened: Compile fails with error:
Compiling playground v0.0.1 (/playground)
error[[E0597]](https://doc.rust-lang.org/nightly/error-index.html#E0597): `b` does not live long enough
--> src/main.rs:14:7
|
8 | 'b: 'f,
| -- lifetime `'b` defined here
...
14 | n(&mut b).await;
| --^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `b` is borrowed for `'b`
15 | }
| - `b` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error
Meta
Playground versions:
Stable channel
Build using the Stable version: 1.66.0
Beta channel
Build using the Beta version: 1.67.0-beta.6
(2022-12-31 51b03459a49d03dbad7d)
Nightly channel
Build using the Nightly version: 1.68.0-nightly
(2023-01-09 3020239de947ec52677e)
Notes
I admit I'm still wobbly with a lot that has to do with lifetimes and futures, but it's my understanding that await
consumes the future so no borrows should exist after that.
The error seems very similar to #98077 but that only discusses drops and not awaits, and implies that await resolves the issue (where here await does nothing). This problem is discussed here: https://stackoverflow.com/questions/70538177/rust-async-borrow-lifetimes where it's suggested HRTBs solve the problem, but I couldn't get the example working. There's a similar discussion here: https://www.reddit.com/r/rust/comments/m1sj3b/problems_with_lifetimes_and_async_fns/ which failed to find a solution.
Without 'f
and 'b
compiling tx
passes but instead there's a lifetime error in the closure about the future outliving the parameter.
My use case is specifically about wrapping transactions in databases, where a user passes in a function and when their function is done executing the transaction (b) is committed or rolled back (I've hit this trying to come up with a transaction wrapper with multiple DB libraries now).
I can work around the issue by moving b into the callback and having the callback return b at the end, but this renders ?
useless and results in a lot more boilerplate.