tezvyn:

Rust's Borrow Checker: Memory Safety at Compile Time

AI-drafted, machine-checkedSource: doc.rust-lang.orgintermediate

Rust's borrow checker is a compiler-time accountant that prevents memory bugs by enforcing ownership rules. It ensures you never access invalid data or have conflicting writes. The main footgun is assuming references are mutable by default; they aren't.

WHY IT EXISTS To eliminate entire classes of common programming bugs, like dangling pointers, null pointer dereferences, and data races, which plague systems languages like C++. The borrow checker achieves this memory safety at compile time, avoiding the runtime performance cost of a garbage collector used in languages like Java or Go.

THE MENTAL MODEL Think of the borrow checker as a strict librarian for your program's memory. Every piece of data has exactly one 'owner'. You can either lend out multiple read-only copies of a book (immutable references, &T) or lend out a single, special copy that can be written in (a mutable reference, &mut T). The librarian (the compiler) will stop you if you try to check out a writeable copy while read-only copies are still out, or vice-versa. This prevents conflicting access.

HOW IT WORKS At compile time, the borrow checker analyzes every variable and reference to track its 'lifetime' and enforce ownership rules. The rules are: first, each value has one variable called its owner. Second, when the owner goes out of scope, the value is dropped. Third, you can have either one mutable reference OR any number of immutable references in a given scope, but never both. Passing a value by reference (&s) is called 'borrowing' because the function uses the data without taking ownership.

WHEN TO USE IT You don't choose to use the borrow checker; it is a fundamental, non-optional part of the Rust compiler that runs on every build. You interact with it by structuring your code to follow its rules, primarily by passing references (& for immutable, &mut for mutable) to functions. This allows different parts of your code to access data without needing to constantly transfer ownership back and forth.

WHEN NOT TO USE IT You cannot disable the borrow checker. However, when its strict single-owner rules are too restrictive for a specific problem, such as implementing a graph where a node might be owned by multiple other nodes, you use special types called smart pointers. Types like Rc (Reference Counted) and Arc (Atomic Reference Counted) allow for shared ownership by moving the ownership checks from compile time to runtime, giving you more flexibility at a small performance cost.

ONE CANONICAL EXAMPLE Attempting to modify data through an immutable reference is a classic borrow checker error. Consider a function that tries to append to a string it borrowed immutably:

fn main() { let s = String::from("hello"); change(&s); // Pass an immutable reference }

fn change(some_string: &String) { some_string.push_str(", world"); // COMPILE ERROR! }

The compiler will fail with an error like cannot borrow *some_string as mutable. The borrow checker sees that change received an immutable reference (&String) but the push_str method requires a mutable one to modify the string. This check prevents unintended side effects. The fix is to explicitly mark the data and the borrow as mutable (let mut s, change(&mut s), some_string: &mut String).

Read the original → doc.rust-lang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.