Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mut borrow persists after await #106688

Closed
andrewbaxter opened this issue Jan 10, 2023 · 3 comments
Closed

Mut borrow persists after await #106688

andrewbaxter opened this issue Jan 10, 2023 · 3 comments
Labels
A-async-await Area: Async & Await A-borrow-checker Area: The borrow checker A-lifetimes Area: Lifetimes / regions AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-async Working group: Async & await

Comments

@andrewbaxter
Copy link

andrewbaxter commented Jan 10, 2023

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.

@andrewbaxter andrewbaxter added the C-bug Category: This is a bug. label Jan 10, 2023
@andrewbaxter
Copy link
Author

andrewbaxter commented Jan 10, 2023

HRTB has the same issue, and I believe I'm using it correctly (based on examples in the linked SO):

struct A;
struct B<'b>(&'b mut A);

async fn m(_b: &mut B<'_>) {}

async fn n2(b: &mut B<'_>) {
    m(b).await
}

trait TxHelper<'a, I, O> {
    type Output: 'a + std::future::Future<Output = O>;

    fn call(self, arg: &'a mut I) -> Self::Output;
}

impl<'a, I, O, F, N> TxHelper<'a, I, O> for N
where
    I: 'a,
    F: 'a + std::future::Future<Output = O>,
    N: FnOnce(&'a mut I) -> F {
    type Output = F;

    fn call(self, arg: &'a mut I) -> F {
        self(arg)
    }
}

async fn tx<
    N: for<'b> TxHelper<'b, B<'b>, ()>,
>(a: &mut A, n: N) {
    let mut b = B(a);
    n.call(&mut b).await;
    drop(b);
}

#[tokio::main]
pub async fn main() {
    tx(
        &mut A, 
        n2,
    ).await;
}

The error isn't there if I get rid of drop(b) FWIW.

@fmease fmease added A-lifetimes Area: Lifetimes / regions A-borrow-checker Area: The borrow checker T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-async-await Area: Async & Await E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example and removed needs-triage-legacy labels Jan 26, 2024
@compiler-errors
Copy link
Member

This works with async closures after simplifying the type bounds:

#![feature(async_closure)]
struct A;
struct B<'b>(&'b mut A);

async fn m(_b: &mut B<'_>) {}

async fn tx<
    N: async FnOnce(&mut B<'_>),
>(a: &mut A, n: N) {
    let mut b = B(a);
    n(&mut b).await;
}

#[tokio::main]
pub async fn main() {
    tx(
        &mut A, 
        async |b: &mut B<'_>| { m(b).await; },
    ).await;
}

@traviscross
Copy link
Contributor

@rustbot labels +AsyncAwait-Triaged +WG-async

We reviewed this today in WG-async triage.

We don't believe this is a bug in the borrow checker. In the example given, the bounds would need to be higher ranked. That's not possible to express currently. What you'll want to use here is async closures (currently on nightly).

Since this isn't a bug in the borrow checker, let's go ahead and close this.

@rustbot rustbot added AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. WG-async Working group: Async & await labels Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-borrow-checker Area: The borrow checker A-lifetimes Area: Lifetimes / regions AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-async Working group: Async & await
Projects
None yet
Development

No branches or pull requests

6 participants