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
&mut greetingcreates a mutable reference. The variable itself must be declaredmut: you cannot lend out write access to something you yourself may not change.- The parameter type
&mut Stringsays "I will modify your String, and you keep it". *n = 0;assigns through the reference to the value it points to. Method calls likes.push_strauto- deref, as with shared references.
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
- any number of shared references
&T, or - exactly one mutable reference
&mut T,
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
&mut xborrowsxmutably;xmust be declaredmut;*r = vwrites through the reference.- The rule: any number of
&T, or exactly one&mut T, never both, for the same value at the same time. - It prevents use-after-free via reallocation ("iterator invalidation") and, across threads, data races.
- A borrow lasts until its last use; reordering often fixes conflicts.
- E0596 (not mutable), E0499 (two mutable), E0502 (shared and mutable) are the errors to recognize.
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.