Part 2 Β· The compile-time machine I β borrows & lifetimes
The borrow checker's errors are the curriculum
Nine error codes cover almost everything the borrow checker will ever say to you. Each one reveals a specific piece of the model.
What this lesson is trying to break (3)
- Borrow errors are a wall of noise to be pattern-matched away.
- Every borrow error has a different cause.
- Adding `.clone()` is the general fix.
You have the model. This lesson makes it operational.
Almost every borrow-checker error you will meet is one of nine codes. Each one corresponds to a specific piece of the model, so learning them is not memorising error strings β it is a diagnostic index into Part 2.
Run rustc --explain E0502 in your terminal at some point today. The compiler
ships an explanation for every code, with examples, and it is the best-hidden
teaching resource the Rust project has.
The table
Varies: which pair of things collided
| Code | Says | What actually happened | Usual fix |
|---|---|---|---|
| E0382 | use of moved value | You used a binding after it was moved away. | Borrow instead of moving; or clone; or restructure so the move is last. |
| E0499 | cannot borrow as mutable more than once | Two &mut to the same data overlap. | Shorten one region; split the borrow into disjoint fields; use indices instead of references. |
| E0502 | cannot borrow as mutable, also borrowed as immutable | A & and a &mut overlap. | Finish using the shared borrow before mutating; compute values first, then mutate. |
| E0505 | cannot move out, it is borrowed | You moved a value while a reference to it was still live. | Let the borrow end first; or clone. |
| E0506 | cannot assign to borrowed | You overwrote a value while a reference to it was live. | Same as E0505 β end the borrow first. |
| E0507 | cannot move out of shared reference | You tried to take ownership of something you only borrowed. | .clone(); borrow instead; or Option::take / mem::replace / HashMap::remove. |
| E0515 | cannot return reference to local | The reference points into a frame that is about to vanish. | Return owned data. Never annotations. |
| E0597 | does not live long enough | A value’s scope ends before a reference to it is used. | Move the declaration outward, shrink the use, or own the data. |
| E0716 | temporary value dropped while borrowed | You borrowed from a temporary that died at the semicolon. | let-bind the temporary first. |
The top six are the shared-XOR-exclusive rule from 2.1, in six different disguises. The bottom three are region problems from 2.3.
Notice the shape of that table. Six of the nine are the same rule. They differ only in which two references collided and which one the compiler reported first. If you can see that, borrow errors stop feeling like nine unrelated problems.
The three that need more than a table
E0507 β cannot move out of a shared reference
The most common error for people arriving from garbage-collected languages, because in those languages there is no difference between βlook atβ and βtakeβ.
struct Config { name: String }
fn take_name(c: &Config) -> String {
c.name // error[E0507]
}
You have a &Config β permission to look. Returning c.name would move the
String out, leaving a hole in a struct you do not own. The compiler is
protecting the owner, who has no idea this is happening.
Three fixes, and the choice is a real decision:
fn take_name(c: &Config) -> String { c.name.clone() } // allocate
fn take_name(c: &Config) -> &str { &c.name } // borrow β no allocation
fn take_name(c: Config) -> String { c.name } // demand ownership
The middle one is usually right and usually not the one people reach for first.
std::mem::take Β· std::mem::replace Β· Option::take
Someone needed to move a value out of a place they only have &mut to.
You cannot leave a hole β but you can swap in a replacement.
mem::take puts Default::default() there, mem::replace puts a value you
supply, and Option::take leaves None.
Seeing these is a reliable sign that the author is restructuring ownership
rather than reaching for .clone(), and it is a good pattern to be able to
name in review.
E0515 β cannot return reference to local variable
fn make() -> &String {
let s = String::from("hi");
&s // error[E0515]
}
Every newcomer tries to fix this with lifetime annotations. Nothing in the lifetime system can fix it, and understanding why is a good test of lesson 2.3.
The reference points into makeβs stack frame. That frame is destroyed on
return. There is no region containing βafter this function returnsβ over which
s is valid, so no annotation can describe one. The problem is not the
description; it is the program.
Return String. Or take a &mut String parameter so the buffer belongs to the
caller.
E0716 β temporary value dropped while borrowed
Straight out of 1.4.
let r = &String::from("hi").len(); // hmm
let name = &config().name; // error[E0716]
config() returns a value with no name. It is a temporary; it dies at the
semicolon; name would dangle.
let cfg = config(); // now it has a place
let name = &cfg.name; // fine β borrows from a local
The fix is always the same: give the temporary a name. When you see E0716, look for the expression that produced a value nobody bound.
Beacon drill
Read each snippet and name the error code before opening the answer. Speed matters more than certainty here β you are training recognition, not derivation.
let mut v = vec![1, 2, 3];
let a = &mut v;
let b = &mut v;
a.push(4);E0499 β two mutable borrows. aβs region extends to a.push(4), and b is
created inside it.
Note that if you deleted a.push(4), this would compile: aβs region would be
empty after creation, so nothing overlaps.
let s = String::from("hi");
let r = &s;
drop(s);
println!("{r}");E0505 β cannot move out of s because it is borrowed.
drop(s) moves s (see 1.3), and rβs region
extends past it to the println!. A move counts as a mutation for the aliasing
rule.
fn first(v: &Vec<String>) -> String {
v[0]
}E0507 β cannot move out of index of Vec<String>, which is behind a shared
reference.
v[0] is a place expression; using it as a value tries to move the String out
of the vector. Fix with v[0].clone() or change the return type to &str and
write &v[0].
let mut scores = std::collections::HashMap::new();
scores.insert("a", 1);
let first = scores.get("a").unwrap();
scores.insert("b", 2);
println!("{first}");E0502 β scores borrowed as immutable by get, then mutably by insert,
with the shared borrow still live at the println!.
This is the Vec example from 2.1 wearing a different collection. Once you see
that shape β borrow, mutate, use the borrow β you will spot it instantly, and
it is by a distance the most common borrow error in real code.
fn longest_line(text: &str) -> &str {
let owned = text.to_uppercase();
owned.lines().max_by_key(|l| l.len()).unwrap()
}E0515 β returning a reference to a local. The &str borrows from owned,
which dies at the end of the function.
The interesting part is that the signature looks fine β it takes a &str and
returns a &str, and elision ties them together. The problem is that the
returned reference does not actually borrow from text at all; it borrows from
the local owned created inside.
Fix: return String, or restructure so the returned slice really does come from
text.
Write down the diagnostic procedure you will actually use when you hit a borrow error. Three or four steps, in order, in your own words.
Mine, for comparison β but write yours first: (1) which two things overlap? (2) which one can I make end sooner? (3) if neither, can I make the disjointness visible β separate fields, indices, split methods? (4) only then, is cloning or owning the honest answer?
What you should now be able to say
- Nine codes cover the field, and six are one rule in different disguises.
- E0507 is the look versus take distinction, and
&stris often the fix people miss. - E0515 is unfixable by annotation β the frame is gone.
- E0716 means an unnamed temporary; bind it.
rustc --explain E0XXXexists and is good.- Fix borrow errors by shortening regions and making disjointness
visible, before reaching for
.clone().
Next: 2.6 β The escape hatches. What do you do when the rule is genuinely too strict for the data structure you need?