Skip to content

Variable lifetime is wrongly tracked in async fn #98077

Closed

Description

@thalesfragoso
async fn foo() {
    let mut f = None;
    let value = 0;
    f = Some(async { value });
    core::mem::drop(f);
}

Playground. Error:

  |
4 |     f = Some(async { value });
  |                    --^^^^^--
  |                    | |
  |                    | borrowed value does not live long enough
  |                    value captured here by generator
5 |     core::mem::drop(f);
6 | }
  | -
  | |
  | `value` dropped here while still borrowed
  | borrow might be used here, when `f` is dropped and runs the destructor for type `Option<impl Future<Output = i32>>`
  |
  = note: values in a scope are dropped in the opposite order they are defined

f is manually moved with drop and could not be dropped at the end of the scope after value.

Some bonus weird behavior, this works:

struct Foo;

async fn foo() {
    let mut f = None;
    let value = Foo;
    f = Some(async { value });
    core::mem::drop(f);
}

But if you add #[derive(Copy, Clone)] to Foo, then it fails with the same error. However, this also does not work:

struct Foo;

impl Foo {
    fn push(&mut self) {}
}

async fn foo() {
    let mut f = None;
    let value = Foo;
    f = Some(async { value.push() });
    core::mem::drop(f);
}

So, probably not necessarily connected to Copy.

Activity

  1. eholk commented on Jun 20, 2022

    @eholk
    Contributor

    We discussed this in our triage meeting today (conversation starts here). There are a few more variations that work:

    We can await the value and it works:

    async fn foo() {
        let mut f = None;
        let value = 0;
        f = Some(async { value }.await);
        core::mem::drop(f);
    }

    We can also change the order that f and value are declared:

    async fn foo() {
        let value = 0;
        let mut f = None;
        f = Some(async { value });
        core::mem::drop(f);
    }

    Using async move also works:

    async fn foo() {
        let mut f = None;
        let value = 0;
        f = Some(async move { value });
        core::mem::drop(f);
    }

    I can explain a lot of the behavior, but the fact that drop doesn't help still confuses me.

    In the cases that fail, I think f gets inferred to be something like Option<impl Future + 'a>, so f borrows something with lifetime 'a. There can be a couple reasons for the borrow. In the case of value.push(), this is sugar for Foo::push(&'a value). When value is Copy, then the use of value desugars to *&'a value, but when value is not Copy, then the use of it counts as a move and there is no borrowing.

    Ignoring the call to drop, when we go out of scope we drop variables in reverse order. This means value gets dropped, and then f potentially has a dangling pointer. That's why we're getting the error. This is also why changing the declaration order fixes the bug.

    The part I don't understand is why the compiler doesn't realize f is no longer live after the call to drop(f). This seems like the kind of thing it's normally able to handle. Maybe this has something to do with the fact that we're in an async fn instead of a regular fn?

    @rustbot label +AsyncAwait-Triaged

  2. added
    AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
    on Jun 20, 2022
  3. moved this to On deck in wg-async workon Dec 8, 2022
  4. Enselic commented on Mar 20, 2026

    @Enselic
    Member

    This issue will be closed by #154155. Please see that PR for more info, such as bisect info.

  5. added 2 commits that reference this issue on Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions