Why does order of mutable borrows matter in Rust?

113 views Asked by At

This code compiles (playground link):

use std::collections::HashMap;

fn main() {
    let mut h = HashMap::<char, Vec<i32>>::new();
    h.insert('a', vec![0]);
    
    let first_borrow = h.get_mut(&'a').unwrap();
    first_borrow.push(1);
    let second_borrow = h.get_mut(&'a').unwrap();
    second_borrow.push(2);
}

Changing the order of the code using the borrows (the push() calls)...

let first_borrow = h.get_mut(&'a').unwrap();
let second_borrow = h.get_mut(&'a').unwrap();
first_borrow.push(1);
second_borrow.push(2);

...makes it not compile:

error[E0499]: cannot borrow `h` as mutable more than once at a time
 --> src/main.rs:8:25
  |
7 |     let first_borrow = h.get_mut(&'a').unwrap();
  |                        - first mutable borrow occurs here
8 |     let second_borrow = h.get_mut(&'a').unwrap();
  |                         ^ second mutable borrow occurs here
9 |     first_borrow.push(1);
  |     ------------ first borrow later used here

Moreover, using first_borrow past the instantiation of second_borrow also doesn't compile:

let first_borrow = h.get_mut(&'a').unwrap();
first_borrow.push(1);
let second_borrow = h.get_mut(&'a').unwrap();
second_borrow.push(2);
    
// ...
    
first_borrow.push(1);

This is surprising given what the documentation seems to say about scopes. In the code that compiles, why don't we have two mutable borrows there, too?

In the example that compiles, does Rust see that, after let second_borrow = ..., there's no more mention of first_borrow anywhere, so it unborrows the mutable borrow of first_borrow and thus retains a single borrow across the whole scope of main()?!

1

There are 1 answers

3
AudioBubble On BEST ANSWER

In the code that compiles, why don't we have two mutable borrows there, too?

The short answer is that two mutable borrows of the same piece of data cannot be in scope simultaneously, and the first example does not violate that restriction. Note that this is a corollary of the "one big restriction" of mutable references which is "you can have only one mutable reference to a particular piece of data in a particular scope." See the References and Borrowing section in The Rust Programming Language.

You first example compiles because first_borrow goes out of scope before second_borrow comes into scope. "Going out of scope" is synonymous with a variable not being referenced for the remainder of a scope. I don't know the low level details, but here is how I think of that example.

    // first_borrow comes into scope
    let first_borrow = h.get_mut(&'a').unwrap();
    first_borrow.push(1);
    // first_borrow goes out of scope
    // second_borrow comes into scope
    let second_borrow = h.get_mut(&'a').unwrap();
    second_borrow.push(2);
    // second_borrow goes out of scope

For your second example that does not compile, we can see that the scopes of first_borrow and second_borrow cross.

    // first_borrow comes into scope
    let first_borrow = h.get_mut(&'a').unwrap();
    // second_borrow comes into scope
    let second_borrow = h.get_mut(&'a').unwrap();
    // !!! both first_borrow and second_borrow are in scope now !!!
    first_borrow.push(1);
    // first_borrow goes out of scope
    second_borrow.push(2);
    // second_borrow goes out of scope

In the example that compiles, does Rust see that, after let second_borrow = ..., there's no more mention of first_borrow anywhere, so it unborrows the mutable borrow of first_borrow and thus retains a single borrow across the whole scope of main()?!

Effectively, yes. I don't think of this as unborrowing though. As worded above, I believe the term is that first_borrow goes out of scope.

For example, you could have written the first example like this.

    {
        let first_borrow = h.get_mut(&'a').unwrap();
        first_borrow.push(1);
    }
    {
        let second_borrow = h.get_mut(&'a').unwrap();
        second_borrow.push(2);
    }

And of course the second example cannot be written in such a way because the borrows cross each other.