Reading Rust

Part 2 · The compile-time machine I — borrows & lifetimes

`&mut` does not mean mutable

The keyword is misnamed, and the misnaming costs people months. Read it as "exclusive" and a whole category of confusion disappears.

35 minrefutation textPOEbeacon training
What this lesson is trying to break (3)
  • `&T` means the data cannot change.
  • `&mut T` is the mutable version of `&T`, and mutation is the point.
  • Passing a `&mut` to a function moves it away permanently.

This lesson is short and it may be the highest ratio of value to length on the site. The problem it fixes is a naming mistake.

Unlearn thiscarried over from every language where const/final/readonly means immutable
You think

&T is an immutable reference: the data behind it cannot change. &mut T is the mutable one. The distinction is about whether you may write, the same way const works in TypeScript or final in Java.

Wrong here

&T does not guarantee the data will not change. It is trivial to disprove: Cell::set, RefCell::borrow_mut, Mutex::lock and every atomic all mutate through &self. If & meant immutable, none of those could exist.

Instead

The real axis is sharing, not mutation. &T is a shared reference: others may hold one too. &mut T is an exclusive reference: while it exists, nothing else in the entire program can see this data. Mutation permission is a consequence — it is safe to write precisely when you are the only observer.

The Rust community knows about this. You will hear people say “unique reference” or “exclusive reference” in preference to “mutable reference”, and the reference documentation increasingly uses shared and mutable rather than immutable and mutable. Renaming the keyword was considered and rejected as too disruptive. So the name stayed wrong, and everyone learns around it.

Start reading it correctly now and you skip the confusion entirely.

The proof

Predict firstcommit in writing, then look
use std::cell::Cell;

fn bump(counter: &Cell<u32>) {
    counter.set(counter.get() + 1);
}

fn main() {
    let c = Cell::new(0);
    bump(&c);
    bump(&c);
    println!("{}", c.get());
}

Note that c is not declared mut, and bump takes a shared reference.

Why &mut T is not Copy

Once you read &mut as exclusive, a rule that looks arbitrary becomes forced.

&T is Copy. You can hand out as many as you like — sharing is exactly what it permits.

&mut T is not Copy, and it cannot be. A copy would give you two exclusive references to the same data, and “exclusive” would then mean nothing. The restriction is not a safety tax bolted on; it is what the word means.

let mut x = 5;
let a = &mut x;
let b = a;          // moves — a is now dead
// println!("{a}"); // E0382: use of moved value
*b += 1;

&mut behaves like an owned value because, for the duration of its region, it effectively is the owner.

Reborrowing — why this doesn’t ruin everything

If &mut moves, how does this work?

fn bump(n: &mut i32) { *n += 1; }

fn main() {
    let mut x = 0;
    let r = &mut x;
    bump(r);
    bump(r);        // r still usable?!
    println!("{x}");
}

By the rule above, the first bump(r) should have moved r away. It compiles and prints 2.

The mechanism is reborrowing. When you pass a &mut where a &mut is expected, the compiler silently inserts &mut *r — a new, shorter-lived exclusive reference derived from r. While the reborrow is alive, r is frozen and unusable. When it ends (at the end of the call), r becomes usable again.

This is one of the most important invisible things the compiler does, and almost nothing explains it, which is why “I thought &mut moves — so why does this work?” is such a common stumble.

Beaconsee this → think this, without decoding it

&mut *r · &mut **r

An explicit reborrow. The author is deliberately creating a shorter-lived exclusive reference, usually because inference did not pick a short enough region on its own, or to force a borrow to end before the next statement.

Seeing it written by hand is a small signal that someone hit a lifetime problem here and solved it deliberately.

Two-phase borrows

One more piece of compiler machinery, because it explains an apparent contradiction you will hit within a week.

let mut v = vec![1, 2, 3];
v.push(v.len());     // compiles

push needs &mut v. v.len() needs &v. Both appear to be live at the same moment, which the rule from 2.1 forbids. Yet it compiles.

The reason is two-phase borrows. A &mut taken for an autoref method call starts in a “reserved” phase, during which shared reads are still permitted. It becomes truly exclusive only when the call actually begins — after the arguments have been evaluated.

You do not need this often, but you need it once: the first time you write this and someone tells you the rule is absolute, you will want to know why the compiler disagrees.

Explain it to yourselfwrite it — thinking it does not count

Rewrite these two sentences without the words “mutable” or “immutable”:

  1. &mut self means the method can modify the struct.”
  2. “You can’t have a mutable and an immutable borrow at the same time.”

Then say which of the two rewrites changed the meaning rather than just the wording. One of them did.

What you should now be able to say

  • The axis is shared versus exclusive, not immutable versus mutable.
  • &T permits sharing; it does not promise the data will not change — Cell::set takes &self.
  • &mut T promises nothing else can observe this, which is what enables both safe mutation and noalias.
  • &mut T cannot be Copy, because two exclusive references is a contradiction.
  • Reborrowing is why passing a &mut does not consume it.
  • Two-phase borrows are why v.push(v.len()) compiles.

Next: 2.3 — Lifetimes are regions of code. That one is the hardest lesson on the site, and the one that pays best.

In the wildgo read the real thing now, while it is fresh
  • std::cell::Cell — set() takes &selfLook at the signature: `pub fn set(&self, val: T)`. A method that mutates, taking a shared reference. If `&` meant immutable this could not exist — so `&` does not mean immutable.
  • Rust RFC 2025 — two-phase borrowsWhy `v.push(v.len())` compiles despite apparently needing &mut and & at once. Skim the motivation section only. Useful for explaining why a rule that sounds absolute has a documented, principled exception.
Scheduled for recall4 items added to your review queue

These are not shown to you now on purpose. They will surface in Review tomorrow, mixed in among items from other parts of the course — not grouped by topic. Sorting out which idea applies is the part that transfers.