Under The Hood
backend rust Rust 1.75+ (edition 2021)

Rust borrow checker and lifetimes

Last updated
Prerequisites:
Rust ownership basics
Understanding of references and generics
  • rust
  • borrow-checker
  • lifetimes
  • ownership
  • memory-safety
  • miri

Read at your depth

The practical view

Every value in Rust has an owner; when the owner goes out of scope, the value is dropped. References let code use a value without taking ownership: &T (shared borrow, many readers) and &mut T (exclusive borrow, one writer). The borrow checker enforces: you cannot have a &mut T while any &T is live, and references must not outlive their referent. Lifetimes (usually elided: fn first<'a>(x: &'a str, y: &'a str) -> &'a str) tell the compiler how long a reference is valid — 'a is the intersection of the inputs' lifetimes. The compiler proves these rules at compile time, so most memory bugs become compile errors.

Legacy vs modern

Invalidating a borrow vs the borrow-checked version

The unsafe/mutable-alias pattern corrupts memory; Rust rejects it at compile time, forcing the shared-ownership design that is safe.

before → after
Aliasing mutation (invalid in Rust)
let mut v = vec![1, 2, 3];
let first = &v[0];       // shared borrow
v.push(4);               // ERROR: cannot borrow v as mutable
println!("{first}");      //   while `first` is still live
Index-based / scoped design
let len = v.len();
let first = v[0];  // copy the value out, no borrow held
v.push(4);         // fine: no outstanding borrow
println!("{first}");

Interview gotchas

Context

This checks precise knowledge of the borrow checker's evolution — the interviewer wants region-based analysis, not just 'NLL made the compiler smarter'.

The mechanical answer

Under lexical lifetimes (pre-2018), a borrow lasted until the end of the enclosing block, so a temporary reference 'poisoned' the whole scope even if its last use was earlier. NLL computes the actual live range: the set of program points where the borrow is used (computed on MIR), so the borrow ends at its last use, not at the block's end. This made patterns like `let x = &v[0]; v.push(1);` legal when x is never read after the push. What NLL still rejects: any use of the reference after the conflicting mutation, and any borrow outliving its referent's region — the fundamental aliasing rules remain. Two-phase borrows (allowing the first phase of a method call to be a shared borrow) are a separate refinement.

Trap

The trap answer is 'NLL lets you mutate while borrowing'. NLL only shortens borrows to their last use; if the reference is actually read after the mutation, the error remains. Another trap: 'NLL is a runtime feature' — it is purely a compile-time region analysis on MIR. Candidates who name RFC 2094, last-use analysis, and the persisting restrictions show they know the checker rather than a release note.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.