RustArena

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:

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

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

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