Skip to content

Can't return reference to passed-in referent that is assigned in a loop #97901

New issue

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

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

Already on GitHub? # to your account

Open
jonhoo opened this issue Jun 8, 2022 · 2 comments
Open
Labels
A-borrow-checker Area: The borrow checker A-control-flow Area: Control flow A-NLL Area: Non-lexical lifetimes (NLL) C-bug Category: This is a bug. fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jonhoo
Copy link
Contributor

jonhoo commented Jun 8, 2022

I tried this code:

struct C;

fn inout<'cfg>(c_out: &'cfg mut C) -> &'cfg C {
    loop {
        *c_out = C;
        let r = &*c_out;
        if true {
            break r;
        }
    }
}

fn main() {
    let mut c = C;
    inout(&mut c);
}

I expected to see this happen: it compiles and runs

Instead, this happened: it fails to compile with the error:

error[E0506]: cannot assign to `*c_out` because it is borrowed
 --> src/main.rs:5:9
  |
3 | fn inout<'cfg>(c_out: &'cfg mut C) -> &'cfg C {
  |          ---- lifetime `'cfg` defined here
4 |     loop {
5 |         *c_out = C;
  |         ^^^^^^^^^^ assignment to borrowed `*c_out` occurs here
6 |         let r = &*c_out;
  |                 ------- borrow of `*c_out` occurs here
7 |         if true {
8 |             break r;
  |                   - returning this value requires that `*c_out` is borrowed for `'cfg`

It's worth noting that the code builds fine if the break is unconditional.

Even though the reproduction is somewhat different, this feels related to #92984

Meta

Happens on 1.61.0 stable, beta, and nightly.

Nightly Rust version from playground:

1.63.0-nightly
2022-06-07 5435ed6916a59e8d5acb

Hard mode

If you're looking for test cases, may also be worth including this one, which "launders" the lifetime through a for<'a>:

struct C;

fn inout<'cfg, F>(c_out: &'cfg mut C, f: F) -> &'cfg C
where
    for<'c> F: Fn(&'c C) -> &'c C,
{
    loop {
        *c_out = C;
        let c = &*c_out;
        let r = f(c);
        if true {
            break r;
        }
    }
}

fn main() {
    let mut c = C;
    inout(&mut c, |c| c);
}
@jonhoo jonhoo added the C-bug Category: This is a bug. label Jun 8, 2022
@ChayimFriedman2
Copy link
Contributor

Looks like the known problem of MIR borrowck with control flow. It compiles with polonius.

@jonhoo
Copy link
Contributor Author

jonhoo commented Jun 9, 2022

Ah, hello #21906/#51545/#54663/#58910 my old friend.

@fmease fmease added A-borrow-checker Area: The borrow checker T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-control-flow Area: Control flow fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. A-NLL Area: Non-lexical lifetimes (NLL) and removed needs-triage-legacy labels Jan 29, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-borrow-checker Area: The borrow checker A-control-flow Area: Control flow A-NLL Area: Non-lexical lifetimes (NLL) C-bug Category: This is a bug. fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants