Skip to content

rustc thinks that two non-intersecting borrows intersect. And fails compilation #139930

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
USSURATONCACHI opened this issue Apr 16, 2025 · 2 comments
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. fixed-by-polonius Compiling with `-Zpolonius` fixes this issue.

Comments

@USSURATONCACHI
Copy link

USSURATONCACHI commented Apr 16, 2025

Here is a minimal example

fn main() {
    let mut data = "hello".to_owned();
    let out = process_data(&mut data);
    println!("{out:?}"); // Expected output: `Some("bye")`
}

/// Process data and return reference to its part.
fn process_data(data: &mut String) -> Option<&str> {
    loop {
        // Modify data in any way
        data.replace_range(0..5, "bye");

        // Access data via immutable reference, and try to reference with inherited lifetime.
        match find_word(data) {
            None => continue,
            Some(word) => return Some(word),
        }
    }
}

// You can use any function that takes a reference, and returns a related reference
fn find_word(data: &str) -> Option<&str> {
    let pattern = "bye";
    data.find(pattern)
        .map(|start| &data[start..(start + pattern.len())])
}

I expected to see this happen:

$ cargo run
Some("bye")

Instead, this happened: I have got a compile error on a perfectly valid code.

[ussur issue]$ rustc src/main.rs
error[E0502]: cannot borrow `*data` as mutable because it is also borrowed as immutable
  --> src/main.rs:11:9
   |
8  | fn process_data(data: &mut String) -> Option<&str> {
   |                       - let's call the lifetime of this reference `'1`
...
11 |         data.replace_range(0..5, "bye");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
14 |         match find_word(data) {
   |                         ---- immutable borrow occurs here
15 |             None => continue,
16 |             Some(word) => return Some(word),
   |                                  ---------- returning this value requires that `*data` is borrowed for `'1`

error: aborting due to 1 previous error

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

The code being valid can be proved by the fact that if we remove the loop - the function compiles perfectly:

// YET this does compile correctly: exactly the same code without a loop.
fn process_data(data: &mut String) -> Option<&str> {
    data.replace_range(0..5, "bye");

    match find_word(data) {
        None => None,
        Some(word) => return Some(word),
    }
}

Moreover, breaking the loop by returning in no way breaks any borrowing rules. If reference is returned - then line that mutates data cannot be executed, and this error message makes no sense. If reference is not returned - this error message also make no sense.

UPD: Moreover, manually unwinding the loop also breaks compilation:

fn process_data_unnwinded(data: &mut String) -> Option<&str> {
    data.replace_range(0..5, "bye");

    match find_word(data) {
        None => {},
        Some(word) => return Some(word),
    };

    data.replace_range(0..5, "bye");

    match find_word(data) {
        None => {},
        Some(word) => return Some(word),
    };

    todo!()
}

Gives:

[ussur@ussur-maibookxseries issue]$ rustc src/main.rs 
error[E0502]: cannot borrow `*data` as mutable because it is also borrowed as immutable
  --> src/main.rs:37:5
   |
29 | fn process_data_unnwinded(data: &mut String) -> Option<&str> {
   |                                 - let's call the lifetime of this reference `'1`
...
32 |     match find_word(data) {
   |                     ---- immutable borrow occurs here
33 |         None => {},
34 |         Some(word) => return Some(word),
   |                              ---------- returning this value requires that `*data` is borrowed for `'1`
...
37 |     data.replace_range(0..5, "bye");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

error: aborting due to 1 previous error

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

Even though immutable borrow ends after match {} ends - rustc for some invalid reason thinks that returning a reference makes it borrowed "forever".

Meta

Bug works the same both on stable and nightly.
rustc --version --verbose of stable:

rustc 1.85.1 (4eb161250 2025-03-15)
binary: rustc
commit-hash: 4eb161250e340c8f48f66e2b929ef4a5bed7c181
commit-date: 2025-03-15
host: x86_64-unknown-linux-gnu
release: 1.85.1
LLVM version: 19.1.7

of nightly:

rustc 1.87.0-nightly (a2e63569f 2025-03-26)
binary: rustc
commit-hash: a2e63569fd6702ac5dd027a80a9fdaadce73adae
commit-date: 2025-03-26
host: x86_64-unknown-linux-gnu
release: 1.87.0-nightly
LLVM version: 20.1.1

Backtrace does not exist since program does not compile.

@USSURATONCACHI USSURATONCACHI added the C-bug Category: This is a bug. label Apr 16, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Apr 16, 2025
@USSURATONCACHI
Copy link
Author

Also checked on two following versions:

rustc 1.86.0 (05f9846f8 2025-03-31)
binary: rustc
commit-hash: 05f9846f893b09a1be1fc8560e33fc3c815cfecb
commit-date: 2025-03-31
host: x86_64-unknown-linux-gnu
release: 1.86.0
LLVM version: 19.1.7
rustc 1.88.0-nightly (38c560ae6 2025-04-15)
binary: rustc
commit-hash: 38c560ae681d5c0d3fd615eaedc537a282fb1086
commit-date: 2025-04-15
host: x86_64-unknown-linux-gnu
release: 1.88.0-nightly
LLVM version: 20.1.2

The issue is exactly the same.

@Urgau
Copy link
Member

Urgau commented Apr 16, 2025

Fixed by Polonius.

@rustbot labels -needs-triage +fixed-by-polonius +A-borrow-checker

@rustbot rustbot added A-borrow-checker Area: The borrow checker fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Apr 16, 2025
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. fixed-by-polonius Compiling with `-Zpolonius` fixes this issue.
Projects
None yet
Development

No branches or pull requests

3 participants