Borrowing and Slices · about 40 min

Mutable References and the Aliasing Rule

Why this matters

Shared references let functions read data they do not own. Often a function also needs to change the caller's data: append to a String, sort a list, update a counter. For that Rust has mutable references, &mut T. They come with the most important rule in the language: you can have many readers or one writer, but never both at once. This rule is what makes Rust programs free of a whole category of bugs, from "iterator invalidation" to data races between threads. It is also the source of most borrow checker errors, so it is worth understanding properly.

Mutable references

fn add_world(s: &mut String) {
    s.push_str(", world");
}

fn reset(n: &mut i32) {
    *n = 0;
}

fn main() {
    let mut greeting = String::from("hello");
    add_world(&mut greeting);
    println!("{greeting}");

    let mut counter = 41;
    counter += 1;
    reset(&mut counter);
    println!("{counter}");
}
hello, world
0

Without mut on the variable:

fn main() {
    let s = String::from("hello");
    let r = &mut s;
    r.push('!');
}
error[E0596]: cannot borrow `s` as mutable, as it is not declared as mutable
 --> main.rs:3:13
  |
3 |     let r = &mut s;
  |             ^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut s = String::from("hello");
  |         +++

The rule: shared XOR mutable

At any point in the program, for any given value, you may have either

but not both. While a mutable reference exists, even the owner may not read or write the value except through that reference.

Two mutable references at once:

fn main() {
    let mut s = String::from("hello");
    let a = &mut s;
    let b = &mut s;
    a.push('!');
    b.push('?');
}
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> main.rs:4:13
  |
3 |     let a = &mut s;
  |             ------ first mutable borrow occurs here
4 |     let b = &mut s;
  |             ^^^^^^ second mutable borrow occurs here
5 |     a.push('!');
  |     - first borrow later used here

A shared and a mutable reference at once:

fn main() {
    let mut s = String::from("hello");
    let r = &s;
    let w = &mut s;
    w.push('!');
    println!("{r}");
}
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> main.rs:4:13
  |
3 |     let r = &s;
  |             -- immutable borrow occurs here
4 |     let w = &mut s;
  |             ^^^^^^ mutable borrow occurs here
5 |     w.push('!');
6 |     println!("{r}");
  |               --- immutable borrow later used here

Both messages follow the same pattern: where the first borrow starts, where the conflicting second borrow happens, and where the first one is used later. That last label is key. The conflict exists only because r (or a) is still used after w (or b) was created.

Why the rule exists: a concrete bug

Here is the classic bug the rule prevents. A reference to an element of a growable list, then a push:

fn main() {
    let mut v = vec![1, 2, 3];
    let first = &v[0];
    v.push(4);
    println!("first = {first}");
}
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> main.rs:4:5
  |
3 |     let first = &v[0];
  |                  - immutable borrow occurs here
4 |     v.push(4);
  |     ^^^^^^^^^ mutable borrow occurs here
5 |     println!("first = {first}");
  |                       ------- immutable borrow later used here

(vec![1, 2, 3] creates a Vec, a growable list on the heap, module 9.) Why is this dangerous? v holds a heap buffer with room for 3 elements. first points into that buffer. push needs room for a fourth, so it allocates a bigger buffer, copies the elements, and frees the old buffer:

before push:                          after push (if it were allowed):

 v: ptr ----> [1|2|3]                  v: ptr ----> [1|2|3|4|_|_]   (new buffer)
               ^
 first --------+                       first -----> [?|?|?]         (freed memory!)

first would now point at freed memory: a use-after-free. In C++ this exact bug ("iterator invalidation") compiles and crashes sometimes, or silently reads garbage. In Rust, push takes &mut self (a mutable borrow of v), which conflicts with the live shared borrow first, and the program does not compile.

The general insight: mutation can invalidate references, often in ways not visible at the call site (reallocation, freeing, changing an enum's variant). Rather than reason about each case, Rust forbids mutation while any other reference is live. One writer with no readers can do anything safely; many readers with no writer cannot interfere with each other.

The same rule, applied across threads, makes data races (two threads touching the same memory, at least one writing, with no synchronization) a compile error. That is module 14.

Borrows end at their last use

A borrow lasts from where it is created to where the reference is last used, not to the end of the block. So this compiles:

fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    println!("{r1} and {r2}");
    let w = &mut s;
    w.push_str(" world");
    println!("{w}");
}
hello and hello
hello world

r1 and r2 are last used in the first println!, so their borrows are over when w is created. This analysis is called non-lexical lifetimes, and the next lesson looks at how the compiler reasons. The practical upshot: often the fix for E0502 is simply to reorder statements so that one borrow is finished before the conflicting one starts.

Mutable references in loops

iter_mut() gives a mutable reference to each element in turn, so you can change a collection in place:

fn main() {
    let mut prices = [10.0, 25.5, 4.25];
    for p in prices.iter_mut() {
        *p *= 1.1;
    }
    println!("{:.2?}", prices);
}
[11.00, 28.05, 4.68]

(for p in &mut prices means the same.) Only one element is mutably borrowed at a time, so the rule is satisfied. {:.2?} applies a precision to Debug output.

&mut is not Copy

Duplicating a mutable reference would create two writers, so &mut T is not Copy. Passing it to a function hands it over temporarily: Rust automatically creates a short reborrow for the duration of the call, which is why you can call exclaim(&mut word) or exclaim(r) several times in a row where r is a &mut String.

Common mistakes

E0596: let without mut. Add mut to the variable you borrow mutably.

E0499: two &mut alive at once. Use one, finish with it, then create the other; or restructure so a single function does both changes.

E0502: reading while a &mut is alive, or mutating while a & is alive. Typically a reference into a collection held across a modification (push, insert, clear). Copy the value out (let first = v[0]; for a Copy type), reorder, or use indexes.

Expecting the owner to be usable while lent out. While w = &mut s is live, even println!("{s}") is a conflict, because printing reads s.

Recap

Going further: read rustc --explain E0502 and rustc --explain E0499. Then try to write a function fn both(a: &mut i32, b: &mut i32) and call it as both(&mut x, &mut x). Predict the error first.

Exercises

Exercise 1 · write code

Modify through &mut

Write fn exclaim(s: &mut String) that appends ! to the String it borrows mutably, and fn double(n: &mut i32) that doubles the integer it points to. The given main prints:

wow!!
21 -> 42

File: main.rs

Starting point

fn main() {
    let mut word = String::from("wow");
    exclaim(&mut word);
    exclaim(&mut word);
    println!("{word}");
    let mut n = 21;
    let before = n;
    double(&mut n);
    println!("{before} -> {n}");
}

Example

expected output

wow!!
21 -> 42
Hint 1

Methods auto-deref: s.push('!') works on a &mut String.

Hint 2

For the integer, assign through the reference: *n *= 2; or *n = *n * 2;.

Reference solution
fn exclaim(s: &mut String) {
    s.push('!');
}

fn double(n: &mut i32) {
    *n *= 2;
}

fn main() {
    let mut word = String::from("wow");
    exclaim(&mut word);
    exclaim(&mut word);
    println!("{word}");
    let mut n = 21;
    let before = n;
    double(&mut n);
    println!("{before} -> {n}");
}

Exercise 2 · write code

Fix the overlapping borrows

This program fails with E0502: it holds a shared reference to the first element of scores while pushing to the vector. Reorder the statements (do not add clone and do not remove any println!) so that it compiles and prints:

first is 10
[10, 20, 30, 40]

File: main.rs

Starting point

fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    scores.push(40);
    println!("first is {first}");
    println!("{:?}", scores);
}

Example

expected output

first is 10
[10, 20, 30, 40]
Hint 1

The shared borrow first lasts until its last use. push needs a mutable borrow.

Hint 2

Print first before calling push.

Reference solution
fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    println!("first is {first}");
    scores.push(40);
    println!("{:?}", scores);
}

Exercise 3 · write code

Normalize in place

Write fn clamp_all(values: &mut [i32; 6], lo: i32, hi: i32) that changes every element below lo to lo and every element above hi to hi, in place. Use for v in values.iter_mut() (each v is a &mut i32). The given main prints:

[0, 0, 5, 10, 10, 7]

File: main.rs

Starting point

fn main() {
    let mut data = [-5, 0, 5, 10, 99, 7];
    clamp_all(&mut data, 0, 10);
    println!("{:?}", data);
}

Example

expected output

[0, 0, 5, 10, 10, 7]
Hint 1

Inside the loop compare *v with the bounds and assign with *v = lo;.

Reference solution
fn clamp_all(values: &mut [i32; 6], lo: i32, hi: i32) {
    for v in values.iter_mut() {
        if *v < lo {
            *v = lo;
        } else if *v > hi {
            *v = hi;
        }
    }
}

fn main() {
    let mut data = [-5, 0, 5, 10, 99, 7];
    clamp_all(&mut data, 0, 10);
    println!("{:?}", data);
}

Exercise 4 · quiz

The rule

At any one point in a program, which combinations of live references to the same value are allowed? Select all that apply.

Answer
  • Any number of shared references (&T)

  • Exactly one mutable reference (&mut T) and nothing else

Shared XOR mutable: either many readers, or one writer, never both. If a writer could coexist with a reader, the reader could see data change under it (or point into a buffer the writer just reallocated). Two writers could interleave and corrupt each other. The compiler checks this rule everywhere, which also makes data races between threads impossible in safe Rust.