Skip to content

Spurious reborrowing errors when returning lifetimes referencing mutable borrows from inside loops #113322

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
dead-claudia opened this issue Jul 4, 2023 · 4 comments
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@dead-claudia
Copy link

dead-claudia commented Jul 4, 2023

I tried this code:

struct Foo {
    value: u32,
}

impl Foo {
    pub fn next<'a>(&'a mut self) -> &'a u32 {
        loop {
            self.value = 123;

            let value = &self.value;

            static COND: bool = true;
            if COND {
                return value;
            }
        }
    }
}

I expected to see this happen: It to compile successfully, like it does without the loop:

struct Foo {
    value: u32,
}

impl Foo {
    pub fn next<'a>(&'a mut self) -> &'a u32 {
        self.value = 123;

        let value = &self.value;

        static COND: bool = true;
        if COND {
            return value;
        }

        panic!()
    }
}

Instead, this happened:

error[E0506]: cannot assign to `self.value` because it is borrowed
  --> <source>:8:13
   |
6  |     pub fn next<'a>(&'a mut self) -> &'a u32 {
   |                 -- lifetime `'a` defined here
7  |         loop {
8  |             self.value = 123;
   |             ^^^^^^^^^^^^^^^^ `self.value` is assigned to here but it was already borrowed
9  |
10 |             let value = &self.value;
   |                         ----------- `self.value` is borrowed here
...
14 |                 return value;
   |                        ----- returning this value requires that `self.value` is borrowed for `'a`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0506`.

Meta

rustc --version --verbose:

rustc 1.70.0 (90c541806 2023-05-31)
binary: rustc
commit-hash: 90c541806f23a127002de5b4038be731ba1458ca
commit-date: 2023-05-31
host: x86_64-pc-windows-msvc
release: 1.70.0
LLVM version: 16.0.2

Also repros on nightly: https://godbolt.org/z/hEdjnz5oE

@dead-claudia dead-claudia added the C-bug Category: This is a bug. label Jul 4, 2023
@dead-claudia
Copy link
Author

This also happens with ref cell borrows and returned mutable references. The returned reference doesn't need to be a mutable value, though it's the most obvious that way.

Example less-minimal repro for same issue, with ref cells
use std::cell::Ref;
use std::cell::RefCell;

type MyCellInner = ();

type MyCell = RefCell<MyCellInner>;

struct MyReader {
    current_cell: Option<MyCell>,
}

fn get_node() -> MyCell {
    ().into()
}

impl MyReader {
    pub fn next<'a>(&'a mut self) -> Ref<'a, MyCellInner> {
        loop {
            self.current_cell = Some(get_node());

            if let Some(node) = &self.current_cell {
                let mapped = Ref::filter_map(node.borrow(), |inner| Some(inner));

                if let Ok(mapped) = mapped {
                    return mapped;
                }
            }
        }
    }
}

This isn't technically a soundness issue in of itself, but I wouldn't be surprised if there's a soundness issue near it.

@dead-claudia
Copy link
Author

Interestingly, this bug does not show with -Z polonius in nightly: https://godbolt.org/z/PqPvGE4WK

Possibly related to #70255, if not sharing a similar root cause with it.

@donottellmetonottellyou
Copy link

I have a similar error with this code:

let mut end_ptr = &mut new_node.next;
loop {
    (do stuff)
    ...
    if let Some(node) = end_ptr.as_mut() {
        end_ptr = &mut node.next;
    } else {
        break;
    }
}

...

*end_ptr = Some(Box::new(ListNode {
    val: node1.val,
    next: Some(Box::new(ListNode::new(node2.val))),
}));

Giving me this error:

error[E0506]: cannot assign to `*end_ptr` because it is borrowed
  --> src/main.rs:92:13
   |
84 |             if let Some(node) = end_ptr.as_mut() {
   |                                 ---------------- `*end_ptr` is borrowed here
...
92 |             *end_ptr = Some(Box::new(ListNode {
   |             ^^^^^^^^
   |             |
   |             `*end_ptr` is assigned to here but it was already borrowed
   |             borrow later used here

At least from what I would assume the borrow checker would be concerned, the if-let statement should be out of scope when I try to assign to end_ptr later. Wrapping the loop block in braces doesn't help. It's like a ghost reference!

@donottellmetonottellyou
Copy link

rustc 1.70.0 (90c541806 2023-05-31)
binary: rustc
commit-hash: 90c541806f23a127002de5b4038be731ba1458ca
commit-date: 2023-05-31
host: x86_64-unknown-linux-gnu
release: 1.70.0
LLVM version: 16.0.2

@jieyouxu jieyouxu added A-borrow-checker Area: The borrow checker T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue and removed needs-triage-legacy labels Mar 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for 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