rustreferenceborrow-checkermutable-reference

Why are we allowed to convert mutable reference to immutable reference?


From rust book:

At any given time, you can have either one mutable reference or any number of immutable references.

Consider the following code:

fn main() {
    let mut a = 41;
    let b = &mut a;
    let c: &i32 = &a; // [1]
    let d: &i32 = &b;

    println!("{} {}", b, c); // [2]
    println!("{} {}", b, d);
}

If we try to compile we get:

error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
  --> src\main.rs:43:19
   |
42 |     let b = &mut a;
   |             ------ mutable borrow occurs here
43 |     let c: &i32 = &a; // [1]
   |                   ^^ immutable borrow occurs here
44 |     let d: &i32 = &b;
   |                   -- mutable borrow later used here

... so, the rule about having only one mutable reference checks out.

However, if you comment lines marked [1] and [2], everything compiles fine. Note that in this case b is mutable reference amd d is immutable reference (seems to be the same as c).

Why are we allowed this situation and why this case compiles without violating rule about having either one mutable reference or n immutable references?


Solution

  • Here, the book has simplified things in order to more quickly convey information. In reality, you can actually have as many mutable references as you want.

    let mut a = 41;
    let b = &mut a;
    let c = &mut *b;
    f(c);
    let d = &mut *c;
    let e = c;
    

    The actual restriction is that only one mutable reference can be active at any point. This also applies to the original owner.

    So when c is created above, you enter a region where you can pass c to functions like f, use c to create other borrows like d, or change ownership of c like e. You aren't allowed to do any of those with b until after the last time c is used. And when you create d, you enter another region where c can't be used. Since d is never used, this region ends immediately and c once again becomes active.

    This should make sense since without it, mutable references would be extremely limited. For example, every function that takes a mutable reference like fn f(x: &mut i32) makes a new temporary borrow from what you pass into it. If it didn't, you would only be able to use the mutable borrow one time.

    This explains why your original code doesn't work. You're trying to use a while b is active.

    Here's your second version:

    fn main() {
        let mut a = 41;
        let b = &mut a;
        // let c: &i32 = &a; // [1]
        let d: &i32 = &b;
    
        // println!("{} {}", b, c); // [2]
        println!("{} {}", b, d);
    }
    

    I'm going to make two changes that don't change any of the borrowing relationships. First, let d: &i32 = &b; gets automatically expanded by the compiler to let d: &i32 = &*b;. If it didn't, this would try to assign a &&mut i32 to a variable of type &i32 and would not compile. Second, the println! macro adds a reference to each of its arguments for convenience. This is something only a macro can do invisibly; when functions take references, it is always explicit. I'm replacing println! with a function so that you can see how each variable is actually borrowed.

    fn main() {
        let mut a = 41;
        let b = &mut a;
        let d: &i32 = &*b;
    
        print(&b, &d);
    }
    
    fn print(x: &&mut i32, y: &&i32) {
        println!("{} {}", *x, *y);
    }
    

    From what I wrote above, this would seem to not work, since you're using b and then using d right after. But there is one other thing that mutable references (as well as the owner and immutable references) are allowed to do: produce any amount of immutable references. As soon as one immutable reference is given out, you enter a region where you can use the immutable reference and create more immutable references. In this case, b creates one immutable reference to its contents (essentially &a, with type &i32) and one immutable reference to itself (with type &&mut i32).

    So with that in mind, the code above goes like this:

    Just before main ends, the two references given to print and the reference d expire, making b active again. b then expires, making a active again. Then a is dropped, and main ends.