Skip to content

1.96 beta regression: "lua does not live long enough", take 2 #155902

Closed

Description

@theemathas

Turns out that the #154988 regression wasn't completely fixed by the #154994 PR. (That regression was in turn caused by #154074, which fixes the #153850 regression).

Regression found by the 1.96 beta crater run.

[INFO] [stdout] error[E0597]: `lua` does not live long enough
[INFO] [stdout]   --> src/transpile/lua.rs:51:30
[INFO] [stdout]    |
[INFO] [stdout] 18 |         #[tracing::instrument(level = "debug", name = "eval_lua", skip_all, ret)]
[INFO] [stdout]    |                                                                                 -
[INFO] [stdout]    |                                                                                 |
[INFO] [stdout]    |                                                                                 `lua` dropped here while still borrowed
[INFO] [stdout]    |                                                                                 ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `(LuaValue<'_>,)`
[INFO] [stdout] ...
[INFO] [stdout] 25 |             let lua = Lua::new();
[INFO] [stdout]    |                 --- binding `lua` declared here
[INFO] [stdout] ...
[INFO] [stdout] 51 |             let lua_result = lua
[INFO] [stdout]    |                              ^^^ borrowed value does not live long enough
[INFO] [stdout] ...
[INFO] [stdout] 62 |             self.handle_lua_result(dbg!(lua_result)).map_err(|err| {
[INFO] [stdout]    |                                    ---------------- a temporary with access to the borrow is created here ...
[INFO] [stdout]    |
[INFO] [stdout]    = note: the temporary is part of an expression at the end of a block;
[INFO] [stdout]            consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
[INFO] [stdout] help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
[INFO] [stdout]    |
[INFO] [stdout] 62 ~             let x = self.handle_lua_result(dbg!(lua_result)).map_err(|err| {
[INFO] [stdout] 63 |                 handler.receive(err.clone());
[INFO] [stdout] 64 |                 err
[INFO] [stdout] 65 ~             }); x
[INFO] [stdout]    |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] For more information about this error, try `rustc --explain E0597`.

cc @dianne

Activity

  1. added
    A-destructorsArea: Destructors (`Drop`, …)
    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)
    C-bugCategory: This is a bug.
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    A-fmtArea: `core::fmt`
    on Apr 28, 2026
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Apr 28, 2026
  3. theemathas commented on Apr 28, 2026

    @theemathas
    ContributorAuthor

    Minimized (requires edition 2021, not 2024):

    #[derive(Debug)]
    struct Foo;
    impl Drop for Foo {
        fn drop(&mut self) {}
    }
    
    #[derive(Debug)]
    struct Bar<'a>(&'a Foo);
    impl Drop for Bar<'_> {
        fn drop(&mut self) {}
    }
    
    fn main() {
        let foo = Foo;
        let bar = Bar(&foo);
        drop(dbg!(bar))  // No semicolon
    }
    Error output on beta
    error[E0597]: `foo` does not live long enough
      --> src/main.rs:15:19
       |
    14 |     let foo = Foo;
       |         --- binding `foo` declared here
    15 |     let bar = Bar(&foo);
       |                   ^^^^ borrowed value does not live long enough
    16 |     drop(dbg!(bar))  // No semicolon
       |          --------- a temporary with access to the borrow is created here ...
    17 | }
       | -
       | |
       | `foo` dropped here while still borrowed
       | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `(Bar<'_>,)`
       |
    help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
       |
    16 |     drop(dbg!(bar));  // No semicolon
       |                    +
    
    For more information about this error, try `rustc --explain E0597`.
    

    This is the same as #154988 (comment) except that Foo also implements Drop.

  4. added
    S-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issue
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Apr 28, 2026
  5. theemathas commented on Apr 28, 2026

    @theemathas
    ContributorAuthor

    This code (based on the expansion of dbg!) errors in edition 2021, and I don't understand why:

    struct Foo;
    impl Drop for Foo {
        fn drop(&mut self) {}
    }
    
    struct Bar<'a>(&'a Foo);
    impl Drop for Bar<'_> {
        fn drop(&mut self) {}
    }
    
    fn main() {
        let foo = Foo;
        let bar = Bar(&foo);
        drop(match (bar,) {
            args => args,
        })
    }
    Error output
    error[E0597]: `foo` does not live long enough
      --> src/main.rs:13:19
       |
    12 |     let foo = Foo;
       |         --- binding `foo` declared here
    13 |     let bar = Bar(&foo);
       |                   ^^^^ borrowed value does not live long enough
    14 |     drop(match (bar,) {
       |                ------ a temporary with access to the borrow is created here ...
    ...
    17 | }
       | -
       | |
       | `foo` dropped here while still borrowed
       | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `(Bar<'_>,)`
       |
    help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
       |
    16 |     });
       |       +
    
    For more information about this error, try `rustc --explain E0597`.
    
  6. dianne commented on Apr 28, 2026

    @dianne
    Member

    Oh no, is this because of drop order on the unwind path? If Foo doesn't implement Drop, there's no conflict because we don't put StorageDeads in cleanup blocks. But if Foo does implement Drop, we end up with a drop for foo on the unwind path before the drop for the (bar,) temp due to Rust 2021 tail expr scopes. My idea was that we'd move out of that temp before it's a problem, but if we panic before then, that might not be the case.

    I tested this hunch and it seems maybe correct? The conflicting location is the drop terminator for foo in a cleanup block. In contrast, for the reproducer in #154988 (comment) (i.e. for pre-#154994 dbg! and no Drop impl for Foo), the conflicting location was the StorageDead for foo on the happy path.

    This really isn't something we should have to worry about in dbg!.. maybe it's time to give up on tricky solutions and just do it with super let? Though with super let in the state it's in right now it'd still be a bit tricky, unfortunately.

  7. dianne commented on Apr 28, 2026

    @dianne
    Member

    As for how we can end up unwinding between creating the (bar,) temporary and moving out of it, as far as I can tell it's because we first move bar into a temporary to be used as the operand for the tuple constructor, which is dropped immediately after constructing the tuple. Of course, this can't actually panic at runtime because it's always uninitialized at that point; aiui drop elaboration will replace that drop with a goto. But since borrowck happens first, the cfg still has that edge there (and I assume if we did move drop elaboration first, we'd use a FalseUnwind to keep the static semantics the same.. though I wouldn't say it's particularly easy to reason about this way).

  8. 12 remaining items

  9. orlp commented on May 17, 2026

    @orlp
    Contributor

    It appears that wrapping the evaluated tuple in an identity function solves this problem. That is, replacing this line:

    match ($($processed,)+) {

    with

    match $crate::convert::identity(($($processed,)+)) { 
  10. theemathas commented on May 18, 2026

    @theemathas
    ContributorAuthor

    Just a {} block is sufficient to get rid of the error. This doesn't make any sense.

    // edition 2021 or earlier
    
    struct Foo;
    impl Drop for Foo {
        fn drop(&mut self) {}
    }
    
    struct Bar<'a>(&'a Foo);
    impl Drop for Bar<'_> {
        fn drop(&mut self) {}
    }
    
    // This compiles
    fn works() {
        let foo = Foo;
        let bar = Bar(&foo);
        drop(match {(bar,)} {
            args => args,
        })
    }
    
    // This errors
    fn fails() {
        let foo = Foo;
        let bar = Bar(&foo);
        drop(match (bar,) {
            args => args,
        })
    }
  11. theemathas commented on May 18, 2026

    @theemathas
    ContributorAuthor

    Comparison of the MIR of the two functions

    The difference in the MIR is where the control flow goes if the destructor of Bar panics. This destructor seems to be, for some reason, ran immediately after Bar is moved into the tuple.

    $ diff mir_dump/foo.{works,fails}.1-1-000.built.after.mir
    1c1
    < // MIR for `works` after built
    ---
    > // MIR for `fails` after built
    3c3
    < fn works() -> () {
    ---
    > fn fails() -> () {
    41c41
    <         drop(_7) -> [return: bb1, unwind: bb13];
    ---
    >         drop(_7) -> [return: bb1, unwind: bb10];
    79c79
    <         drop(_6) -> [return: bb8, unwind: bb15];
    ---
    >         drop(_6) -> [return: bb8, unwind: bb13];
    100c100
    <         drop(_6) -> [return: bb15, unwind terminate(cleanup)];
    ---
    >         drop(_6) -> [return: bb13, unwind terminate(cleanup)];
    104,111d103
    <         drop(_2) -> [return: bb14, unwind terminate(cleanup)];
    <     }
    < 
    <     bb14 (cleanup): {
    <         drop(_1) -> [return: bb15, unwind terminate(cleanup)];
    <     }
    < 
    <     bb15 (cleanup): {
    
    MIR of works
    // MIR for `works` after built
    
    fn works() -> () {
        let mut _0: ();
        let _1: Foo;
        let mut _3: &Foo;
        let _4: &Foo;
        let mut _5: (Bar<'_>,);
        let mut _6: (Bar<'_>,);
        let mut _7: Bar<'_>;
        scope 1 {
            debug foo => _1;
            let _2: Bar<'_>;
            scope 2 {
                debug bar => _2;
                let _8: (Bar<'_>,);
                scope 3 {
                    debug args => _8;
                }
            }
        }
    
        bb0: {
            StorageLive(_1);
            _1 = Foo;
            FakeRead(ForLet(None), _1);
            StorageLive(_2);
            StorageLive(_3);
            StorageLive(_4);
            _4 = &_1;
            _3 = &(*_4);
            _2 = Bar::<'_>(move _3);
            StorageDead(_3);
            FakeRead(ForLet(None), _2);
            StorageDead(_4);
            StorageLive(_5);
            StorageLive(_6);
            StorageLive(_7);
            _7 = move _2;
            _6 = (move _7,);
            drop(_7) -> [return: bb1, unwind: bb13];
        }
    
        bb1: {
            StorageDead(_7);
            PlaceMention(_6);
            StorageLive(_8);
            _8 = move _6;
            _5 = move _8;
            drop(_8) -> [return: bb3, unwind: bb10];
        }
    
        bb2: {
            FakeRead(ForMatchedPlace(None), _6);
            unreachable;
        }
    
        bb3: {
            StorageDead(_8);
            goto -> bb4;
        }
    
        bb4: {
            _0 = std::mem::drop::<(Bar<'_>,)>(move _5) -> [return: bb5, unwind: bb9];
        }
    
        bb5: {
            StorageDead(_5);
            drop(_2) -> [return: bb6, unwind: bb11];
        }
    
        bb6: {
            StorageDead(_2);
            drop(_1) -> [return: bb7, unwind: bb12];
        }
    
        bb7: {
            StorageDead(_1);
            drop(_6) -> [return: bb8, unwind: bb15];
        }
    
        bb8: {
            StorageDead(_6);
            return;
        }
    
        bb9 (cleanup): {
            drop(_5) -> [return: bb10, unwind terminate(cleanup)];
        }
    
        bb10 (cleanup): {
            drop(_2) -> [return: bb11, unwind terminate(cleanup)];
        }
    
        bb11 (cleanup): {
            drop(_1) -> [return: bb12, unwind terminate(cleanup)];
        }
    
        bb12 (cleanup): {
            drop(_6) -> [return: bb15, unwind terminate(cleanup)];
        }
    
        bb13 (cleanup): {
            drop(_2) -> [return: bb14, unwind terminate(cleanup)];
        }
    
        bb14 (cleanup): {
            drop(_1) -> [return: bb15, unwind terminate(cleanup)];
        }
    
        bb15 (cleanup): {
            resume;
        }
    }
    
    MIR of fails
    // MIR for `fails` after built
    
    fn fails() -> () {
        let mut _0: ();
        let _1: Foo;
        let mut _3: &Foo;
        let _4: &Foo;
        let mut _5: (Bar<'_>,);
        let mut _6: (Bar<'_>,);
        let mut _7: Bar<'_>;
        scope 1 {
            debug foo => _1;
            let _2: Bar<'_>;
            scope 2 {
                debug bar => _2;
                let _8: (Bar<'_>,);
                scope 3 {
                    debug args => _8;
                }
            }
        }
    
        bb0: {
            StorageLive(_1);
            _1 = Foo;
            FakeRead(ForLet(None), _1);
            StorageLive(_2);
            StorageLive(_3);
            StorageLive(_4);
            _4 = &_1;
            _3 = &(*_4);
            _2 = Bar::<'_>(move _3);
            StorageDead(_3);
            FakeRead(ForLet(None), _2);
            StorageDead(_4);
            StorageLive(_5);
            StorageLive(_6);
            StorageLive(_7);
            _7 = move _2;
            _6 = (move _7,);
            drop(_7) -> [return: bb1, unwind: bb10];
        }
    
        bb1: {
            StorageDead(_7);
            PlaceMention(_6);
            StorageLive(_8);
            _8 = move _6;
            _5 = move _8;
            drop(_8) -> [return: bb3, unwind: bb10];
        }
    
        bb2: {
            FakeRead(ForMatchedPlace(None), _6);
            unreachable;
        }
    
        bb3: {
            StorageDead(_8);
            goto -> bb4;
        }
    
        bb4: {
            _0 = std::mem::drop::<(Bar<'_>,)>(move _5) -> [return: bb5, unwind: bb9];
        }
    
        bb5: {
            StorageDead(_5);
            drop(_2) -> [return: bb6, unwind: bb11];
        }
    
        bb6: {
            StorageDead(_2);
            drop(_1) -> [return: bb7, unwind: bb12];
        }
    
        bb7: {
            StorageDead(_1);
            drop(_6) -> [return: bb8, unwind: bb13];
        }
    
        bb8: {
            StorageDead(_6);
            return;
        }
    
        bb9 (cleanup): {
            drop(_5) -> [return: bb10, unwind terminate(cleanup)];
        }
    
        bb10 (cleanup): {
            drop(_2) -> [return: bb11, unwind terminate(cleanup)];
        }
    
        bb11 (cleanup): {
            drop(_1) -> [return: bb12, unwind terminate(cleanup)];
        }
    
        bb12 (cleanup): {
            drop(_6) -> [return: bb13, unwind terminate(cleanup)];
        }
    
        bb13 (cleanup): {
            resume;
        }
    }
    
  12. theemathas commented on May 18, 2026

    @theemathas
    ContributorAuthor

    I've filed this weirdness as #156713.

  13. added a commit that references this issue on May 19, 2026
  14. theemathas commented on May 23, 2026

    @theemathas
    ContributorAuthor

    This issue is fixed by #156772.

  15. added this to the 1.96.0 milestone on May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlA-borrow-checkerArea: The borrow checkerA-destructorsArea: Destructors (`Drop`, …)A-fmtArea: `core::fmt`A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)C-bugCategory: This is a bug.P-criticalCritical priorityS-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issueT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-libsRelevant to the library team, which will review and decide on the PR/issue.regression-from-stable-to-betaPerformance or correctness regression from stable to beta.

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions