Why does Rust allow many immutable borrows but only one mutable?
Short answer: The combination Rust forbids is not reading plus writing, it’s aliasing plus mutation. Shared borrows can’t change anything, so no reader can invalidate another and any number of them can be alive at once. A mutable borrow can change everything, including where the value’s memory lives, so it has to be exclusive: while it exists, nothing else may be looking at the value.
Readers can’t invalidate each other
Section titled “Readers can’t invalidate each other”A & hands out read access and nothing else. Two shared borrows see the same bytes from the first
line to the last, and neither can be the reason the other goes wrong. That’s why the rule puts no
number on them: any count of & borrows is fine. The compiler only asks whether a &mut overlaps
one.
A writer can move the memory under a reader’s feet
Section titled “A writer can move the memory under a reader’s feet”Borrow one element of a Vec, then push to it. From a fresh project — cargo new borrow-why, then
replace src/main.rs with the whole of this. The project name doesn’t matter, because nothing in the
output depends on it:
fn main() { let mut scores = vec![1, 2, 3]; let first = &scores[0]; scores.push(4);
println!("{first}");}error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &scores[0]; | ------ immutable borrow occurs here4 | scores.push(4); | ^^^^^^^^^^^^^^ mutable borrow occurs here5 |6 | println!("{first}"); | ----- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.The push is refused, and the reason is not tidiness. push can grow the buffer: allocate a bigger
block, copy the elements across, free the old one. A reference taken before the push would then point
at freed memory. Here the Vec starts at exactly its capacity of three, so the fourth element forces
that move:
fn main() { let mut scores = vec![1, 2, 3]; println!("{}", scores.capacity()); scores.push(4); println!("{}", scores.capacity());}36Without the rule, the first program compiles and first is left pointing at the freed buffer: the
classic dangling-reference bug, in the same family as the invalidated C++ iterator. Rust turns it
into a compile error instead.
The everyday shape: iterating while pushing
Section titled “The everyday shape: iterating while pushing”The same collision, in its usual form:
fn main() { let mut numbers = vec![1, 2, 3];
for n in &numbers { numbers.push(n * 2); }}error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable --> src/main.rs:5:9 |4 | for n in &numbers { | - -------- | | | | | immutable borrow occurs here | _____| immutable borrow later used here | |5 | | numbers.push(n * 2); | | ^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here6 | | } | |_____- this for loop borrows `numbers` immutably, preventing mutation within its body | = help: consider using an index-based loop instead, or collecting modifications into a separate collection
For more information about this error, try `rustc --explain E0502`.The loop holds a shared borrow of numbers for its whole body, so the push inside it overlaps a
reader. The iterator points into the buffer; a push in the middle of the loop can move that buffer,
and the iterator would keep reading the old one. The compiler’s own suggestion is the shape of the
fix: an index-based loop, or collecting the changes first. When the change doesn’t depend on reading
other elements, iter_mut() is shorter still.
What exclusivity buys
Section titled “What exclusivity buys”A function that takes &mut Vec<i32> can resize the buffer without asking:
fn add_four(scores: &mut Vec<i32>) { scores.push(4);}
fn main() { let mut scores = vec![1, 2, 3];
add_four(&mut scores);
println!("{scores:?}");}[1, 2, 3, 4]Exclusivity is a promise from the caller: nothing else is reading the value while the function runs,
so growing the buffer is safe. That is the promise the first example couldn’t make, and it travels in
the signature. At the call site the binding needs mut;
why the keyword is required has that error.
When you want to change a value while looping over it
Section titled “When you want to change a value while looping over it”Take the mutable references one element at a time instead of borrowing the whole collection:
fn main() { let mut numbers = vec![1, 2, 3];
for n in numbers.iter_mut() { *n *= 2; }
println!("{numbers:?}");}[2, 4, 6]iter_mut() hands out one &mut i32 per iteration, so the loop itself is the single exclusive
borrow, and *n *= 2 writes through it. When a change depends on reading several elements at once,
collect the decisions first and apply them after the loop. When even that doesn’t fit, Cell and
RefCell move the borrow check to run time; that trade gets its own chapter, later.
Related
Section titled “Related”- What is the difference between & and &mut? — the two kinds of borrow, with the real E0502 and E0499 messages
- What are the rules of borrowing? — the two rules this page explains
- How to borrow a value — the
&that starts all of this - Borrowing — the rest of this chapter
- Next in this chapter: what is the borrow checker?