Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 17: Borrowing, Constrained Access

You will understand

  • Aliasing XOR mutation as a formal invariant
  • Why iterator invalidation is impossible in Rust
  • How NLL changed Rust 2018 borrow scoping

Reading time

40 min
+ 25 min exercises
Aliasing Problem

Two Readers Is Stable, Reader Plus Writer Is Not

Shared readers 42 both observers see the same stable value Reader + writer 42 → ? one view assumes stability while another changes the cell
Iterator Safety

Why Rust Rejects Iterator Invalidation

iterator points into current buffer push may reallocate storage old iterator would dangle after move Rust rejects the conflicting borrow before runtime
#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];       // shared borrow of v
v.push(4);                // ERROR: &mut borrow while &v lives
println!("{first}");      // shared borrow used after conflict
}
Owner created v owns the Vec. Heap buffer at address A.
&T borrow first borrows into v's buffer. It assumes buffer stability.
E0502 push needs &mut v but first's &v is still live. push may reallocate, moving the buffer.
Dangling prevented If push reallocated, first would point to freed memory. Borrow checker prevents it.

In Your Language: Iterator Invalidation

Rust — compile-time prevention
#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);          // COMPILE ERROR
println!("{first}"); // borrow still live
}
Python — runtime crash possible
v = [1, 2, 3]
it = iter(v)
next(it)          # 1
v.append(4)       # works! but...
# iterator may give unexpected results
# no compile-time guard

Walk Through: Why push Invalidates a Reference

Step 1: Vec allocates let mut v = vec![1, 2, 3]; — Vec allocates a heap buffer large enough for 3 elements (capacity may be 3 or 4). v owns this buffer.
Step 2: Borrow into the buffer let first = &v[0];first is a &i32 pointing directly into the heap buffer. It assumes the buffer is at a stable address.
Step 3: Push may reallocate v.push(4); — If capacity is exhausted, Vec allocates a new, larger buffer, copies all elements, and frees the old one. first now points to freed memory → dangling pointer.
Step 4: Rust prevents it The borrow checker sees that first holds &v (shared borrow) while push requires &mut v (exclusive). Since `first` is used after `push`, their borrow regions overlap → E0502 at compile time. No runtime crash possible.

Readiness Check - Borrowing Confidence

Before proceeding, self-check your ability to reason about aliasing and mutation.

SkillLevel 0Level 1Level 2Level 3
Explain aliasing XOR mutationI memorize the phrase onlyI can explain many-readers/one-writerI can identify why a specific borrow conflict occursI can predict borrow regions before compiling
Debug borrow conflictsI try random editsI can fix one obvious E0502 caseI can choose between borrow narrowing and ownership transferI can refactor APIs to make borrow discipline obvious
Design mutation flow safelyI mutate where convenientI can isolate mutation blocksI can structure code to minimize overlapping borrowsI can review code for hidden iterator invalidation risks

Target Level 2+ before moving to Chapter 21.

Compiler Error Decoder - Constrained Access

Error codeWhat it usually meansTypical fix direction
E0502Immutable and mutable borrows overlapNarrow borrow lifetimes with smaller scopes and earlier last-use
E0499Two mutable borrows coexistRefactor into one mutation path at a time
E0506Assigned to a value while it was still borrowedDelay assignment until borrow ends or clone required data first

Always ask: “Which borrow must stay live here?” Then eliminate or shorten the other one.

Step 1 - The Problem