Skip to content

[E0521] coroutine and *FnOnce closures should own upvars and there shall not be escaping borrows #140132

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

Open
dingxiangfei2009 opened this issue Apr 21, 2025 · 0 comments
Labels
C-bug Category: This is a bug. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged.

Comments

@dingxiangfei2009
Copy link
Contributor

dingxiangfei2009 commented Apr 21, 2025

I tried this code:

fn fnonce(_: impl FnOnce()) {}

fn foo(x: &i32) {
    let mut a = &3;
    fnonce(move || {
        let b = 5;
        a = &b;
        //~^ ERROR borrowed data escapes outside of coroutine
    });
}

I expected to see this happen:
This should compile. The following is the current MIR of the closure at the analysis phase.

// MIR for `foo::{closure#0}` after analysis

fn foo::{closure#0}(_1: {[email protected]:5:12: 5:19}) -> () {
    debug a => (_1.0: &i32);
    let mut _0: ();
    let _2: i32;
    let mut _3: &i32;
    let _4: &i32;
    scope 1 {
        debug b => _2;
    }

    bb0: {
        StorageLive(_2);
        _2 = const 5_i32;
        FakeRead(ForLet(None), _2);
        StorageLive(_3);
        StorageLive(_4);
        _4 = &_2;
        _3 = &(*_4);
        (_1.0: &i32) = move _3;
        StorageDead(_3);
        StorageDead(_4);
        _0 = const ();
        StorageDead(_2);
        return;
    }
}

Note that the closure value _1 has a correct type for a FnOnce so that it is entirely owned by the closure body.

Instead, this happened:
This unfortunately does not compile.

error[E0521]: borrowed data escapes outside of closure
 --> fnonce.rs:7:9
  |
4 |     let mut a = &3;
  |         ----- `a` declared here, outside of the closure body
...
7 |         a = &b;
  |         ^^^^--
  |         |   |
  |         |   borrow is only valid in the closure body
  |         reference to `b` escapes the closure body here

This boils down to a borrow not living long enough due to the StorageDead(_2) statement, at which _1 is still alive.

A similar situation also applies to coroutines as implemented today. See this test suite

Meta

rustc --version --verbose:

rustc 1.88.0-dev
binary: rustc
commit-hash: unknown
commit-date: unknown
host: x86_64-unknown-linux-gnu
release: 1.88.0-dev
LLVM version: 20.1.2

The commit hash should be 49e5e4e3a5610c240a717cb99003a5d5d3356679.

Possible way forward

We could generate a separate body for FnOnce variant of closures, when requested, and apply a MIR transformation early to lift the upvars into their own locals. This is a known technique in #135527.

Backtrace

N/A

@dingxiangfei2009 dingxiangfei2009 added the C-bug Category: This is a bug. label Apr 21, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Apr 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged.
Projects
None yet
Development

No branches or pull requests

2 participants