Skip to content

Chapter 3 of 8

Borrowing and the Borrow Checker

Borrowing is the act of letting another piece of code access your value without taking ownership, by handing out a reference. A shared reference &T permits read-only access, while a mutable reference &mut T permits exclusive write access. The cornerstone rule — sometimes called "aliasing XOR mutability" — is that at any program point, a value may be accessed either through any number of shared references or through exactly one mutable reference, but never both at once. This rule is fundamental to Rust's safety guarantees: by preventing both data races at compile time and the kind of unsynchronized aliasing that can break compiler optimizations, it underpins the correctness of transformations like LLVM's noalias attribute on &mut T.

The enforcement of these rules is the job of the borrow checker, which runs at compile time on every Rust program. It rejects code that would create a dangling reference — a reference pointing to memory that has been freed or to a value that has gone out of scope — long before the program runs, so safe Rust cannot produce use-after-frees or double-frees. The borrow checker's lifetime tracking has steadily improved: the NLL (Non-Lexical Lifetimes) refinement, stabilized in Rust 1.31 in December 2018, ends a reference's lifetime at its last use rather than at the closing brace of its enclosing block, allowing more natural code to compile. An even more precise analyzer called Polonius is under development, designed to compute loan lifetimes through dataflow analysis and accept programs that NLL cannot.

References must always point to valid data, and the compiler enforces this even in subtle cases. You cannot, for instance, return a reference to a local variable from a function, because the local is dropped at the closing brace and would leave the reference dangling — the compiler rejects such code at compile time. Inside a single function, you may create any number of immutable references to a value, but the moment a mutable reference appears, all immutable references must be gone, and only one mutable reference may exist. These rules apply to iteration as well: v.iter() yields &T by immutably borrowing the vector, v.iter_mut() yields &mut T by exclusively borrowing it, and v.into_iter() consumes the collection entirely, yielding owned T values.

All chapters
  1. 1Ownership Rules and Memory
  2. 2Moves, Copy, and Clone
  3. 3Borrowing and the Borrow Checker
  4. 4Lifetimes, Variance, and PhantomData
  5. 5Slices, Strings, and Deref Coercion
  6. 6Interior Mutability and Runtime Enforcement
  7. 7Shared Ownership Across Threads
  8. 8Patterns, Iteration, and Unsized Types

Drill it

Reading is not remembering. These come from the Rust Ownership And Borrowing Explained deck:

Q

What are the three ownership rules in Rust?

Each value in Rust has an owner. There can only be one owner at a time. When the owner goes out of scope, the value is dropped.

Q

What happens to a value when its owner goes out of scope in Rust?

It is dropped — its drop function runs and its memory is freed.

Q

Which standard library function is called when a value goes out of scope?

drop, from the Drop trait, is called automatically at the end of the variable's scope.

Q

What is a "move" in Rust?

A transfer of ownership: assigning a value to another variable or passing it to a function moves the value, invalidating the source binding.