Why does this not pass the borrow checker?

The following code does not pass the borrow checker:

fn main() {
    let mut value: f64 = 0.0;

    let r = &mut value; // Mutable borrow

    let immut_ref = &value; // Immutable borrow
    println!("{}", immut_ref); // Use immutable reference

    *r += 1.0; // Error: cannot use `r` while `immut_ref` is active
}

In this code, an immutable reference to value is acquired and used after acquiring an immutable reference. Following that, the mutable reference is incremented.

I am of the opinion that this code is completely safe. The value that immut_ref sees is consistent, and immut_ref is not used after the increment of r.

Why is it not possible to take an immutable reference to a value on which a mutable reference exists, as long as the immutable reference is not used following any subsequent usage of the mutable reference?

Are there any examples analogous to this code which would result in memory corruption? What exactly is the borrow checker trying to prevent here?

Any input appreciated.

Don't have time for a detailed answer, in short:

If you can prove it's safe with let immut_ref = &value;, then you always can change it to let immut_ref = &*r;. If you can't change it, then you (probably) can't prove it's safe.

Thanks for the suggestion. Would you be able to explain when you have time why that works?

In the code I was trying to get to work which led to this question, I was actually taking a mutable reference r to self.x.y, then calling a method which used &self, then later assigning to r.

I imagine your solution probably doesn't work for this case.

The short answer is that's how it is defined. &mut T is an exclusive reference: while it exists, no other references to the place must be usable (other than references derived from it).

But that might not sound very useful until you understand why it is defined like that.

Now consider the following:

fn main() {
    let mut value: f64 = 0.0;

    let r = &mut value;

    let immut_ref = &value;
    *r += 1.0;
    
    println!("{}", immut_ref); // 1.0

}

Would you allow this code?

Probably not, right: the value of immut_ref changes, which is bad. That's what the borrow checker is there to prevent.

You might argue that your code is different, because immut_ref's whole liveness scope is "between" two mutations of the place. But the borrow checker has no way to see that: it can only locally track when a value is "actually mutated": value does not see how the derived reference r is used, only how long it exists.

And if you reborrow r (derive immut_ref from r) instead of trying to borrow value behind r's back, it does work:

fn main() {
    let mut value: f64 = 0.0;

    let r = &mut value;

    let immut_ref = &*r;
    // the borrow checker can see that `r` can not be written to here
    println!("{}", immut_ref);

    *r += 1.0; // works
}
5 Likes