Understanding borrow checking and references of references

Hi everyone! I need help understanding something.

The basic rule of references is that I can have either one mutable reference or many shared ones. So this obviously fails to compile:

fn main() {
    let mut x = vec![0];
    let m = &mut x;
    let i = &x;
    println!("{}", i[0]);
    println!("{}", m[0]);
    m[0] == 100;
}

But I was surprised to see that if I take a shared reference to the mutable reference, then the code does compile, for example:

fn main() {
    let mut x = vec![0];
    let m = &mut x;
    let i = &m;
    println!("{}", i[0]);
    println!("{}", m[0]);
    m[0] == 100;
}

In this case i is a (shared?) reference to the mutable reference. In any case, it seems to me that I'm having two references to the same vector, although one is an indirect one. And one of them is mutable. Why can the compiler accept this indirection? And how is it safe?

Thanks in advance for the clarification.

The rule of either one mutable reference or many shared ones, applies to active references. A reference is remains active only until its last use.

In the first example, m is created, then i is attempting another borrow while m is still active (because m is used later in the function). That is clearly an error.

In the second example, i is a borrow of m (not of x), so it's creation is fine, but m cannot then be used as a &mut reference until i has stopped being used. That is fine, because m is only used after the last use of i (following which i is no longer active).

Actually, looking closer at your second example, all your uses of m are non-mutating, so the ordering doesn't matter at all (you are just taking further shared references to m, which is fine because you can have multiple). If you perform a mutation through m and then use i afterwards, you will get an error (playground example).

1 Like

It helps to imagine borrows as a tree [1]. When you have a leaf like i, it can be used at will, but it cannot exist beyond any point where its parent is invalidated by an exclusive reference (such as an assignment or passing m to a function that takes &mut T).


  1. Which is formalized with Tree Borrows. ↩︎

3 Likes

To expand on this, there's a lot of implicit shared reborrowing and shared-reference-taking going on in the example.[1] When you index on m in a non-mutable place, it calls something like

<Vec<_> as Index<usize>>::index(&*m, 0)

(And then the println! macro takes a reference to the result, and the == operator also implicitly takes shared references to the operands.)


  1. Here's a brief introduction to reborrowing. ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.