Rust's Interior Mutability: Mutating 'Immutable' Data
Interior mutability lets you modify data through an immutable reference, moving Rust's borrow checks from compile-time to runtime. It's used in single-threaded code when the compiler can't verify safe access.
WHY IT EXISTS Rust's compile-time borrow checker is conservative by design. To guarantee memory safety, it sometimes rejects correct programs because it cannot statically prove their safety. Interior mutability provides a controlled escape hatch for these valid but complex scenarios, allowing the programmer to defer safety checks until runtime.
THE MENTAL MODEL Think of Rust's ownership rules as a contract. Usually, you negotiate with the compiler, which then enforces the rules at compile time for zero runtime cost. With interior mutability, you tell the compiler, "Trust me, I'll handle the safety checks myself at runtime." The contract is still enforced, but by a runtime guard (like RefCell<T>) instead of the compiler.
HOW IT WORKS Types that provide interior mutability, like RefCell<T>, use unsafe code internally to bypass the compiler's static checks, wrapping it in a safe public API. Instead of compile-time errors, RefCell<T> performs checks when you call methods like borrow() or borrow_mut(). If you violate the borrowing rules (one mutable OR many immutable references), the program panics. This moves enforcement from compile time to runtime.
WHEN TO USE IT Use this pattern in single-threaded scenarios where you have an immutable value but need to mutate part of it. This is common in APIs where you can only pass immutable references (&self), or in complex data structures where the compiler can't understand the borrowing dynamics, but you can guarantee they are safe. A classic example is for mocking objects in tests or implementing internal caches.
WHEN NOT TO USE IT Avoid using interior mutability as a default or to sidestep learning Rust's ownership system. Compile-time checks are always preferable because they catch bugs earlier and have no runtime performance cost. RefCell<T> is strictly for single-threaded code; using it across threads results in a compile error. For multithreaded interior mutability, use Mutex or RwLock.
ONE CANONICAL EXAMPLE A library needs to track how many times a specific function is called. The function only has an immutable reference to its parent object, &self. Normally, you can't modify data through &self. By wrapping an integer counter inside a RefCell<u32>, the function can call borrow_mut() on the cell to increment the counter, even though the outer object is immutable. The check for a valid mutable borrow happens at that moment.
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.