Skip to content
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

No compiler error message when more than one mutable ref to mutable var is declared. #113243

Closed
jraldrin opened this issue Jul 1, 2023 · 7 comments
Labels
C-bug Category: This is a bug.

Comments

@jraldrin
Copy link

jraldrin commented Jul 1, 2023

I was trying to write some example code to show that rust won't allow more than one
mutable ref to a mutable var. I came up with an example that I thought should generate
a compiler error but did not.

The code appended does not generate a compiler error.

I do get a compiler error when I comment out
*mref2 = 4 and uncomment
*mref1 = 4

I tried this code:

<fn main() {

    let mut mutable_int = 100;
    let mref1 = &mut mutable_int;
    let mref2 = &mut mutable_int;
    //*mref1 = 4;
    *mref2 = 4;
    //assert_eq!(*mref1, *mref2);
}
>

I expected to see this happen: cannot borrow mutable_int as mutable more than once at a time

Instead, this happened: compiled without error

Meta

rustc --version --verbose:

<version>
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


<!--
Include a backtrace in the code block by setting `RUST_BACKTRACE=1` in your
environment. E.g. `RUST_BACKTRACE=1 cargo build`.
-->
<details><summary>Backtrace</summary>
<p>

None
<backtrace>

@jraldrin jraldrin added the C-bug Category: This is a bug. label Jul 1, 2023
@LoganDark
Copy link

LoganDark commented Jul 1, 2023

This is probably because of either NLL (non-lexical lifetimes) or Polonius, but basically the borrow checker will only raise an error if you actually try to violate the borrowing rules - that is, using both mutable lifetimes, rather than just one. If you use just one, then only one exists at a time, and all is right with the world (because we can just say that the other ones were dropped before you used that one).

I'm leaving out all the semantics of stacked/tree borrows of course, which are just theories that want to take the current rough state of things and extrapolate it into a comprehensive and well-defined ruleset, but they are still pretty good approximations of what you can expect from Rust today if you look at the Stacked Borrows documents linked here.

@LoganDark
Copy link

Actually, there is apparently a name for this specific concept - "two-phase borrows", where there is a gap between when you take a reference to something (the first phase) and first use the reference to that thing (the second phase). Therefore taking two mutable references to the same thing in succession won't raise any error as long as only one of them enter enters phase two.

@jraldrin
Copy link
Author

jraldrin commented Jul 1, 2023

Well obviously I'm in my Rust early days so its going to be a while before I did that deep but I cant help but wonder if my comments were not clear.

It confusing to me that the following will generate a compiler error

let mut mutable_int = 100;
let mref1 = &mut mutable_int;
let mref2 = &mut mutable_int;
*mref1 = 4;

and the following does not

let mut mutable_int = 100;
let mref1 = &mut mutable_int;
let mref2 = &mut mutable_int;
//*mref1 = 4;
*mref2 = 4;

They seem like similar cases to me (das Rust newb).

Thx for quick response on a Saturday.

@LoganDark
Copy link

Well obviously I'm in my Rust early days so its going to be a while before I did that deep

Just trying to help

I cant help but wonder if my comments were not clear. It confusing to me that the following will generate a compiler error and the following does not

For the first one, it's because you tried to use mref1 after creating mref2. mref2's creation invalidates mref1; since there can only be one mutable borrow at a time, creating a new one causes all the others to become invalid. (Mainly, anyway.)

For the second one, mref2 invalidates mref1, but you use mref2 so it's fine.

@LoganDark
Copy link

Don't be worried that Rust is hard to learn, it is. Stuff like this happens all the time, only thing you can do is keep on trying. Most likely this is not a bug in Rust, and is just something that the documentation didn't explain very well. Maybe you could join the community Discord server and try to find out what happened :)

@asquared31415
Copy link
Contributor

The basics of non-lexical lifetimes is that a borrow can be "ended early" if it needs to.

In your case 1, mref1 must extend at least until *mref1 = 4; so that the write can happen. Because that borrow must last that long, mref2 cannot be created, since that would mean that mref1 and mref2 exist at the same time.

In case 2, mref1 can be ended instantly. You never use it, so there's no reason for it to stick around. Since mref1 ends instantly, mref2 can be created, and used as you'd expect.

I have the mental model of "don't interleave XYX" when writing code like this. If X is mref1 and Y is mref2, case one is:
create X
create Y
use X

case 2 is
create X
create Y
use Y

Since the first one interleaves creating and using two different mutable references, it can't work. The second case doesn't interleave anything at all, so it can work. (Note: there are exceptions, but this model works for most code you want to write)

@ChrisDenton
Copy link
Member

I hope the replies help, jraldrin. I'm going to close this now as it seems this is really more of a help question than a compiler bug. You might also be interested in the users forum, official discord server or other Rust communities which are great at answering questions from new users.

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.
Projects
None yet
Development

No branches or pull requests

4 participants