Skip to content

Mut borrow persists after await #106688

Closed
Closed
@andrewbaxter

Description

@andrewbaxter

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitA-borrow-checkerArea: The borrow checkerA-lifetimesArea: Lifetimes / regionsAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.E-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable ExampleT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-asyncWorking group: Async & await

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions