Concept · Rust
Interior mutability
Mutate through a shared reference with Cell, RefCell, Mutex or atomics — the aliasing rule still holds, but it's checked at runtime.
3 minRefCellCellRc<RefCell<T>>Arc<Mutex<T>>
The borrow checker allows mutation only through a unique &mut T. Some designs need shared owners who also mutate: a cache read by many parts of a program, a counter bumped from several places, a graph whose nodes point at each other. Interior mutability types allow mutation through a shared &T by enforcing the "one writer" rule some other way.
| Type | How it stays safe | Threads |
|---|---|---|
Cell<T> | Never hands out references: get copies, set replaces | One |
RefCell<T> | Counts borrows at runtime; panics on a conflict | One |
Mutex<T> | One locker at a time; others block | Many |
RwLock<T> | Many readers or one writer; others block | Many |
AtomicU64, AtomicBool… | Hardware atomic instructions | Many |
use std::cell::RefCell;
let log = RefCell::new(Vec::new());
log.borrow_mut().push("started"); // runtime-checked &mut
println!("{:?}", log.borrow()); // runtime-checked &They're usually combined with shared ownership: Rc<RefCell<T>> on one thread, Arc<Mutex<T>> or Arc<RwLock<T>> across threads. Which ones may cross threads is decided by Send and Sync.
Costs and cautions
- The check moves from compile time to runtime: a conflicting
RefCellborrow is a panic, a conflicting lock is a wait (or a deadlock). - Keep borrows and lock guards short — borrow, use, drop — and never hold a
std::sync::Mutexguard across an.await. RefCelleverywhere is a design smell. Often one owner holding the data, with others sending it messages, is simpler.
See the smart pointers lesson for when to use each.