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.

TypeHow it stays safeThreads
Cell<T>Never hands out references: get copies, set replacesOne
RefCell<T>Counts borrows at runtime; panics on a conflictOne
Mutex<T>One locker at a time; others blockMany
RwLock<T>Many readers or one writer; others blockMany
AtomicU64, AtomicBool…Hardware atomic instructionsMany
rust
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 RefCell borrow 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::Mutex guard across an .await.
  • RefCell everywhere 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.

Learn it in depth