References and Borrowing

Borrowing Rules

Rust's borrow checker enforces two core borrowing rules at compile time on every reference (&T and &mut T) in a program:

  1. Aliasing XOR Mutability: At any given point in execution, you may have either any number of immutable references (&T) or at most one mutable reference (&mut T) to a particular piece of data—never both at the same time.
  2. References Must Always Be Valid (No Dangling References): Every reference must be outlived by the owner of the value it points to, and the owner cannot be moved or destroyed while any reference to it is still active.

Rule 1: Aliasing XOR Mutability

In systems programming, combining aliasing (multiple pointers referring to the same memory location) with mutation (writing through one of those pointers) is the root cause of data races across threads and iterator/reference invalidation within a single thread.

Rust prevents both by making shared aliasing (&T) and mutation (&mut T) mutually exclusive:

Allowed State A — Multiple Readers (`&T`):     Allowed State B — Single Writer (`&mut T`):
  r1: &T ──┐                                     r_mut: &mut T ───> [ Value: T ]
  r2: &T ──┼──> [ Value: T ] (Read-Only)         (No other `&T` or `&mut T` allowed;
  r3: &T ──┘                                      even the owner is locked out!)
Active Borrows on x Read via &x? Read via Owner x? Mutate via &mut x? Mutate or Move Owner x?
None Allowed Allowed Allowed (if mut x) Allowed (if mut x / move)
One or more &x active Allowed Allowed Forbidden (E0502) Forbidden (E0506 / E0505)
One &mut x active Forbidden (E0502) Forbidden (E0502) Allowed (via that &mut x only!) Forbidden (E0499 / E0506)
fn main() {
    let mut scores = vec![10, 20, 30];

    // Phase 1: Multiple shared readers (`&Vec<i32>`)
    let r1 = &scores;
    let r2 = &scores;
    println!("Phase 1 (Shared readers): r1={r1:?}, r2={r2:?}");
    // `r1` and `r2` are never used after this line -> Phase 1 ends here!

    // Phase 2: Single exclusive writer (`&mut Vec<i32>`)
    let w = &mut scores;
    w.push(40);
    println!("Phase 2 (Exclusive writer): w={w:?}");
    // `w` is never used after this line -> Phase 2 ends here!

    // Phase 3: Owner `scores` has full access again
    println!("Phase 3 (Owner): scores={scores:?}");
}
Phase 1 (Shared readers): r1=[10, 20, 30], r2=[10, 20, 30]
Phase 2 (Exclusive writer): w=[10, 20, 30, 40]
Phase 3 (Owner): scores=[10, 20, 30, 40]

Rule 2: References Must Never Outlive Their Owner

A reference is a borrow, and a borrow cannot last longer than the lender:

  1. No returning references to local variables (E0515): When a function exits, its stack frame is popped; any reference pointing to a local variable in that frame would immediately dangle.
  2. No moving the owner while borrowed (E0505): Moving a value to another variable or function while a reference to it is still live is rejected:
// ❌ does not compile
fn main() {
    let data = String::from("borrowed text");
    let r: &String = &data;

    let moved_owner = data; // Error: cannot move out of `data` while `r` borrows it!

    println!("r = {r}, moved_owner = {moved_owner}");
}
error[E0505]: cannot move out of `data` because it is borrowed
 --> src/main.rs:5:23
  |
2 |     let data = String::from("borrowed text");
  |         ---- binding `data` declared here
3 |     let r: &String = &data;
  |                      ----- borrow of `data` occurs here
4 |
5 |     let moved_owner = data;
  |                       ^^^^ move out of `data` occurs here
6 |
7 |     println!("r = {r}, moved_owner = {moved_owner}");
  |                   --- borrow later used here

error: aborting due to 1 previous error