&mut T: Exclusive Write Access Without Ownership
A shared reference (&T) is a read-only lease. A mutable reference (&mut T) lends exclusive write access — the borrow, not the borrow-checker-ownership, is what lets a function modify data it does not own:
fn append_bang(s: &mut String) { // exclusive borrow
s.push_str("!"); // mutation through the reference
} // borrow ends at return
fn main() {
let mut greeting = String::from("hello");
append_bang(&mut greeting); // &mut: mutable borrow
println!("{}", greeting); // "hello!" — the original was changed
}
Two requirements work together here. The reference must be &mut (that is what permits push_str), and the binding must be mut (let mut greeting) — otherwise the compiler rejects creating the mutable borrow at all (error[E0596]: cannot borrow greeting as mutable, as it is not declared as mutable). These are separate decisions with separate keywords, and confusing them is the single most common beginner error.
&mut is exactly what you pass when a function must transform a value in place:
fn normalize(text: &mut String) {
text.make_ascii_lowercase();
text.truncate(text.trim_end().len());
}
fn sort_descending(values: &mut [i32]) { // &mut [T]: mutable slice fat pointer
values.sort_unstable_by(|a, b| b.cmp(a));
}
Note the slice form &mut [T] — a mutable fat pointer that can rearrange elements in the caller's Vec without taking ownership, the in-place counterpart to the read-only &[T]. And the same borrow-checker guarantee from before holds: &mut references are always valid, never dangling, and the mutation happens through one well-defined path.
Reference bindings themselves get the usual treatment: they can be shadowed, stored, returned (with lifetime constraints), and — crucially — &mut is not Copy, so passing one to a function MOVES the reference, transferring exclusive access. The callee holds the sole write permission until it finishes. That transfer is the mechanical basis for the exclusivity rule you will meet next: if the only mutable reference to a value is sitting in some function's frame, no one else can be writing at the same time. Data races become a compile error rather than a midnight production incident.
Look at what &mut costs, because the answer surprises people who expect runtime checks:
assert_eq!(std::mem::size_of::<&i32>(), 8);
assert_eq!(std::mem::size_of::<&mut i32>(), 8);
One machine word each. &mut is not a fat pointer, not a handle with a lock, not a checked capability — it is an address plus a compile-time promise about everyone else. That is the whole performance argument for Rust's aliasing model: exclusivity is proven before the program runs, so nothing is verified while it runs.
The mutation itself usually arrives through a method that asks for &mut self. Vec::push, String::push_str, sort, fill, and swap all take &mut self, and the error you get when the binding was forgotten is precise:
error[E0596]: cannot borrow `orders` as mutable, as it is not declared as mutable
--> src/main.rs:4:5
|
4 | orders.push(order);
| ^^^^^^ `orders` is not declared as mutable
|
help: consider changing this to be mutable
|
3 | let mut orders = Vec::new();
| +++
Read it as two separate questions the compiler already answered: may this binding be borrowed mutably? (no — declare let mut) and does the method want write access? (yes — hence &mut self). Keeping the two questions apart is what lets you reason about larger programs: the binding's mut is a property of your code's intent, while &mut in a signature is a property of the operation being performed.
One more shape appears constantly in real code — mutating while reading the same structure through two accessors:
let mut v = vec![1, 2, 3];
for x in &mut v { *x *= 2; } // one exclusive borrow, element by element
let total: i32 = v.iter().sum(); // borrow ended at the loop — shared read now OK
iter_mut hands out one &mut per element, each disjoint from the others, so the loop mutates in place with no allocation and no cloning; the moment it finishes, the exclusive borrow is dead and ordinary reads resume. Sequence your borrows and the rule stops feeling restrictive.
The Single-Mutator Rule: One &mut XOR Many &
This is the aliasing rule at the heart of Rust's memory safety, and it fits in one sentence:
At any given moment, a value may have either one mutable reference or any number of shared references — never both.
Every violation produces a named error with two spans — where the conflicting borrow started, and where it conflicts:
fn main() {
let mut v = vec![1, 2, 3];
let r1 = &mut v;
let r2 = &mut v; // error[E0499]: cannot borrow `v` as mutable more than once
println!("{:?} {:?}", r1, r2);
}
Mixing the two directions fails the same way:
let mut count = 0;
let r = &count; // shared borrow
count += 1; // error[E0502]: cannot borrow `count` as mutable
println!("{}", r); // because it is also borrowed as immutable
Why so strict? Because the combination is the exact shape of a data race — one party writes while another reads, and the reader sees a torn or stale value. In C/C++ that is a heisenbug found under load; in Rust the aliasing analysis rejects the program before it runs. The rule is not merely defensive: &T and &mut T carry no runtime bookkeeping, so guaranteeing exclusivity statically is what buys zero-cost safety. (The standard library builds thread-safe types on top: &T is Send + Sync shareable across threads precisely because the checker already proved nobody can mutate it while it is shared.)
The rule's reach surprises people. The borrow does not end at the statement — it ends when the reference can no longer be used, and the compiler's non-lexical lifetimes compute that point precisely (see The Borrow Checker Rules for the full model). Two borrows in disjoint code paths are fine; borrows whose live ranges overlap are not. And when you genuinely need mutation through shared access — a cache updated behind &self, a value read from many places — the standard library provides interior mutability types (Cell, RefCell, Mutex, atomics) that enforce the exclusivity rule at runtime instead of compile time, trading the static guarantee for checked flexibility. Reach for them deliberately; they are the escape hatch, not the default.
Here is the two-span diagnostic in full — the shape you should be able to reconstruct from memory:
error[E0499]: cannot borrow `matrix` as mutable more than once at a time
--> src/main.rs:6:13
|
5 | let a = &mut matrix[0];
| -------------- first mutable borrow
6 | let b = &mut matrix[1];
| ^^^^^^^^^^^^^^ second mutable borrow
7 | println!("{:?} {:?}", a, b);
| - first borrow later used here
The final note — "first borrow later used here" — is the actionable half: it tells you the first borrow is still live because you use it after the second one. Swap the two println! arguments' uses, or scope a to end before b is created, and the program compiles. The error is not "two borrows exist"; it is "two borrows overlap in time."
Three tools narrow overlaps in practice:
let mut v = vec![1, 2, 3];
// 1. separate the uses in time (NLL does the rest)
let sum = { let a = &v[0]; let b = &v[1]; a + b }; // both die at the block end
v.push(4); // now legal
// 2. prove disjointness
let (head, tail) = v.split_at_mut(1); // two &mut, no overlap possible
// 3. let the standard library borrow for you
for slot in v.iter_mut() { *slot += 1; }
A subtlety worth having ready: borrows that would naively conflict are often allowed because rustc models phases of an expression. v.push(v.len()) compiles — len runs under a shared borrow during argument evaluation, and the mutable borrow of v only becomes active once the arguments are computed (a two-phase borrow). When it does not compile, the fix is the same as always: compute the value first, then borrow (let n = v.len(); v.push(n);).
Rebinding References: The mut You Keep Missing
A reference is itself a binding — and like all bindings, it is immutable unless declared mut. This produces a distinct family of errors that has nothing to do with the referent:
fn main() {
let mut data = vec![1];
let r = &mut data; // r borrows mutably — data had to be `mut`, and it was
r.push(2); // fine — write THROUGH r
let other = vec![9];
r = &mut other; // error[E0594]: cannot assign to `r`,
} // as it is not declared as mutable
The distinction to hold: data needed mut so a &mut data could be created; r needs its own mut so the reference variable can be reassigned to point somewhere else. One keyword per decision, at two different places. Reassigning the referent through an immutable reference binding is likewise impossible — *r = vec![5]; requires r to be &mut, no matter what the original binding says.
Shadowing interacts with references the way it does with everything:
let mut score = 10;
let r = &mut score; // mutable borrow
*r += 5; // 15 through the reference
let r = &score; // NEW binding: the old borrow ended, so a shared
println!("{}", r); // borrow is now allowed — 15
The second let r hides the first, and by the time the shared borrow is created the mutable one is dead — this is legal and common. It is also how code narrows a long-lived mutable borrow down to a small window: shadow the reference, and its authority ends.
Slices get the mutable treatment uniformly — reborrowing a &mut [T] sub-range is just more slicing:
fn bump_first(values: &mut [i32]) {
let (head, tail) = values.split_first_mut(); // two DISJOINT mutable slices
*head.unwrap() += 1; // head and tail never alias
tail.sort();
}
split_first_mut (and split_at_mut) carve one mutable slice into non-overlapping pieces — the type system encodes "these ranges don't overlap," so both halves can be &mut simultaneously without breaking exclusivity. This is the same rule the compiler enforces, expressed as an API: disjointness proven at the type level permits parallel mutation. When you write code that must mutate two parts of one collection, reaching for split_at_mut is the idiomatic answer — and explaining why it is sound (disjoint borrows) is exactly the kind of reasoning the exclusivity rule is designed to produce.
One behavior of &mut bindings is not obvious until you pass them along: a mutable reference is reborrowed, not moved, when it is handed to a function that expects another &mut:
fn clear(items: &mut Vec<i32>) { items.clear(); }
fn main() {
let mut v = vec![1, 2, 3];
let r = &mut v;
clear(r); // implicit reborrow: `&mut *r`
r.push(9); // still legal — r's authority resumed after the call
}
The compiler inserts &mut *r because a function that stores the reference (fn keep<'a>(x: &'a mut T) with an inferred long lifetime) would move it instead — and the tell is a "v does not live long enough" error where you expected a move. Understanding reborrow explains the everyday magic of &mut chaining: exclusive access can be lent onward and returned without ever being duplicated.
The two remaining diagnostics deserve their exact wording memorized, since both are about the reference variable rather than the data:
error[E0594]: cannot assign to `r`, as it is not declared as mutable
error[E0596]: cannot borrow `data` as mutable, as it is not declared as mutable
E0596 is about creating the borrow (the source binding lacked mut); E0594 is about overwriting the reference variable itself (the reference binding lacked mut). If you can say which of the two you are looking at, you already know which mut to add — and you are thinking the way the checker does: mutability is a property requested at a specific site, never inherited through a name.
Quiz
1. What two conditions must hold to mutate a String through a reference, and what error names each violation?
2. Why does Rust allow either one &mut or many & — but never both at once?
3. What does `let mut r = &mut data;` require, and when may `r` be reassigned?
4. What are split_at_mut / split_first_mut for, and why are they sound?
Flashcards
Question
The exclusivity rule, stated precisely.
Click to reveal answer
Answer
At any moment a value may have EITHER one mutable reference (&mut T) OR any number of shared references (&T) — never both simultaneously. Violations: E0499 (two &mut to one value), E0502 (&mut while a & may still be used). The rule statically eliminates data races with zero runtime cost.
Question
Binding mutability vs reference mutability — the two separate `mut`s.
Click to reveal answer
Answer
`let mut x` — x may be reassigned AND &mut x may be created (E0596 if missing). `let r: &mut T` — writes through r are allowed; r may be rebound only if declared `let mut r` (E0594 if not). Mutating through a reference needs BOTH: a mut binding to borrow from, and a &mut reference to write through.
Question
When does a mutable borrow end — the closing brace or somewhere else?
Click to reveal answer
Answer
At its LAST USE, not the end of the block — non-lexical lifetimes (NLL) compute the borrow's live range from data flow. After the reference can no longer be used, the value is free for other borrows or mutation. This is why `let r = &mut v; use(r); v.push(1);` compiles even in one scope.
Question
How can two &mut borrows of one collection coexist at all?
Click to reveal answer
Answer
When their ranges are provably disjoint: split_at_mut/split_first_mut carve one &mut [T] into non-overlapping &mut slices (the API encodes the proof), so each half can be mutated freely. Direct `&mut v[i]` + `&mut v[j]` fails because the compiler cannot prove i != j.
Question
What is interior mutability, and when do you reach for it?
Click to reveal answer
Answer
Types that allow mutation behind a &T — Cell (no runtime check, single-thread), RefCell (runtime borrow checking: panics on rule violation), Mutex/RwLock/atomics (thread-safe). Used when mutation through shared access is genuinely needed (caches, shared state); it moves the exclusivity check from compile time to runtime.
Revision Notes
Key Takeaways
- 1. &mut T grants exclusive write access without ownership — the callee mutates in place and the borrow ends at its last use
- 2. Exclusivity (one &mut or many &, never both) is what makes data races a compile error — statically, with zero runtime cost
- 3. Two different mut keywords: binding mut gates creating &mut; reference mut gates reassigning the reference itself (E0596 vs E0594)
- 4. NLL ends borrows at their last use, so narrowing a mutable borrow to a small window is often all a rejection needs
- 5. split_at_mut encodes disjointness in the type system — the sanctioned way to mutate two parts of one collection at once
Interview Tips
- • "State the aliasing rule and justify it" — one &mut XOR many &; the mixed case IS a data race; Rust proves it statically at zero runtime cost, which is also why &T is Sync-shareable
- • "&mut vs passing by value?" — &mut borrows exclusively (caller keeps ownership, callee transforms in place); by value moves ownership. Mutating APIs take &mut; consuming APIs take T
- • "My code says 'cannot borrow as mutable because it is also borrowed as immutable' — what now?" — end the shared borrow first: use the value before the mutable borrow, narrow the borrow's scope into a block, or clone; explain NLL ends borrows at last use
- • "How do you mutate two elements of one Vec?" — you can't take two &mut v[i] (compiler can't prove disjointness); split_at_mut / iter_mut / indices collected first — name the mechanism that proves non-overlap
Cheat Sheet
Mutable References (&mut T)
Write through a borrow:
let mut s = String::from("hi"); // binding must be mut (E0596 otherwise)
append_bang(&mut s); // &mut: exclusive borrow, moves the reference
fn append_bang(s: &mut String) { s.push_str("!"); }
Exclusivity: one &mut XOR many & at any moment.
| Error | Shape |
|---|---|
| E0499 | &mut v twice, live ranges overlap |
| E0502 | &mut x while a &x may still be used |
| E0596 | borrow x as mut but x not declared mut |
| E0594 | assign to a reference binding not declared mut |
Ends at last use (NLL), not the closing brace — narrow a borrow by using it earlier / shadowing it.
Disjoint mutation: split_at_mut(&mut [T]) / split_first_mut → two non-overlapping &mut slices; sound because the ranges cannot alias.
Interior mutability (compile-time check → runtime): Cell