Step 4 of 7
The rules: many readers or one writer
Here is the rule at the heart of the borrow checker. For any value, at any moment, you can have either:
- any number of
&references (many readers), or - exactly one
&mutreference (one writer),
but not both at once. (There's a second rule, "references must always be valid", in step 6.)
Many Rust programmers find the names shared reference (&) and exclusive reference (&mut) clearer than "immutable" and "mutable": a &mut is about having the value to yourself.
Many readers are fine, because nobody changes anything:
fn main() {
let motto = String::from("one writer or many readers");
let r1 = &motto;
let r2 = &motto;
let r3 = r1; // & references are Copy: this copies the pointer
println!("{} | {} | {}", r1, r2, r3);
}
one writer or many readers | one writer or many readers | one writer or many readers
Two writers: E0499
fn main() {
let mut log = String::from("start");
let a = &mut log;
let b = &mut log;
a.push_str(" / a");
b.push_str(" / b");
println!("{}", log);
}
error[E0499]: cannot borrow `log` as mutable more than once at a time
--> src/main.rs:4:13
|
3 | let a = &mut log;
| -------- first mutable borrow occurs here
4 | let b = &mut log;
| ^^^^^^^^ second mutable borrow occurs here
5 | a.push_str(" / a");
| - first borrow later used here
A reader and a writer: E0502
This one prevents a real, classic bug:
fn main() {
let mut scores = vec![10, 20, 30];
let first = &scores[0];
scores.push(40);
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 here
4 | scores.push(40);
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("{}", first);
| ----- immutable borrow later used here
Why is this dangerous? push needs room. If the vector's heap block is full, push allocates a bigger block, copies the elements over and frees the old block. first would still point into the old one:
before the push: scores ──► [10, 20, 30] capacity 3: full
▲
first
after the push: scores ──► [10, 20, 30, 40, _, _] a new, bigger block
[10, 20, 30] ◄── first the old block: FREED
Reading first would be a use-after-free. In C++, holding a reference into a std::vector across a push_back is exactly this bug, and it compiles without a word. Rust refuses to compile it. Note that push doesn't take a &mut you can see: method calls borrow the value automatically, &mut for push, & for len.
Why this rule matters even more with threads
A data race happens when two threads access the same memory at the same time, at least one of them writes, and nothing coordinates them. The result depends on timing: data gets corrupted, sometimes, on some machines. "Many readers or one writer" makes data races impossible to compile. You'll see in the threads module that the same rule you're learning here is what makes concurrent Rust safe.
Fixing these errors
- Copy out what you need instead of keeping a reference:
let first = scores[0];copies ani32, so there's no borrow left. - Finish reading before writing: the next step shows that a borrow ends at its last use, so reordering often fixes the error.
- Collect first, change later: you can't push to a vector while looping over it with
for x in &v. Work out what to add in the loop, then add it afterwards.
fn main() {
let mut scores = vec![10, 20, 30];
let first = scores[0]; // a copy of the number, not a borrow
scores.push(40);
println!("first was {}, now {} scores", first, scores.len());
}
first was 10, now 4 scores
Common mistakes
- Holding a reference into a vector while pushing to it (E0502).
- Changing a vector inside
for x in &v(E0502): the loop holds a shared borrow of the whole vector until it ends. - Two
&mutto the same value (E0499), including passing&mut xto a function while another&mut xis still in use.
Your task
Every score of 100 or more earns a bonus round, which is added as a 10 at the end of the list. The program tries to push while looping over &scores, which is E0502. Fix it without .clone(): for example, count the bonus rounds in the loop, then push them after it. It should print:
[120, 80, 100, 45, 10, 10]
Challenge 2
Two mutable references to score exist at the same time (E0499). Fix it so only one writer is active at any moment, for example by using a single &mut, so it prints:
team score: 15
Challenge 3
Write append_doubles. It borrows a vector mutably and appends, at the end, the double of every original element: [1, 2, 3] becomes [1, 2, 3, 2, 4, 6]. You can't push while looping over the vector by reference, so loop over the indexes of the original length instead. No .clone().
Previous: Changing values through &mut with * Next: Borrows end at their last use