Interior Mutability: Cell<T>, RefCell<T>, Mutex<T>, RwLock<T>
- Last updated
- Prerequisites:
- Rust ownership and borrowing
- Understanding of shared vs mutable references
- rust
- interior-mutability
- cell
- refcell
- mutex
- rwlock
Read at your depth
The practical view
Rust's borrow checker normally forbids mutating a value while it's shared. Interior mutability provides a way to mutate through a shared reference, moving the check from compile time to runtime (or to a lock). Cell<T> (Copy types): get()/set() with no borrow tracking. RefCell<T>: borrow()/borrow_mut() with runtime checks (panics on double borrow). Mutex<T>: thread-safe with a lock (lock() returns a guard). RwLock<T>: many readers or one writer. Use Cell/RefCell for single-threaded interior mutability (e.g. counters, caches), Mutex/RwLock for shared state across threads.
The same idea in other frameworks
N/A — Rust language feature.N/A — Rust feature.N/A — Rust feature.N/A — Rust feature.Legacy vs modern
Compile-time borrow error vs RefCell runtime borrow
Mutating through a shared reference is a compile error in safe Rust; RefCell moves the check to runtime and panics on violation.
let x = &v; // ❌ OLD: cannot mutate through a shared reference at compile timelet cell = RefCell::new(vec![1]);
cell.borrow_mut().push(2); // ✅ NEW: runtime-checked exclusive borrowInterview gotchas
Context
This tests the interior-mutability design — interviewers want the copy-in/copy-out mechanism and why RefCell (with borrows) is needed for types that can't be copied.
The mechanical answer
Cell<T> works by replacing the value wholesale: get() copies the value out, set() copies a new value in. There are never references INTO the cell's content exposed to the caller — so there is no aliasing to manage, and no runtime borrow check is needed. This is only sound for Copy types (types that can be duplicated bit-for-bit); for non-Copy types (String, Vec), copying in and out would move the value, which is not possible through a shared reference. RefCell<T> instead exposes borrow()/borrow_mut() that hand out REFERENCES to the content, tracking how many are active with a runtime flag: multiple shared borrows are fine, a mutable borrow requires zero other borrows, and a violation panics. That's why RefCell handles non-Copy types: it never copies, it borrows.
Trap
The naive answer is 'Cell and RefCell are interchangeable'. They have different mechanisms (copy vs borrow) and different type constraints (Copy vs any). Another trap: thinking RefCell's runtime check makes unsafe code unnecessary — it prevents borrow violations but the check is a panic, and using borrow_mut while a borrow is alive still causes a runtime failure, not a compile error. Candidates who explain the copy-vs-borrow distinction show real understanding.
Context
This probes the thread-safety boundary — interviewers want the lock-based vs borrow-flag mechanism and the auto-trait (Send/Sync) differences that make RefCell single-threaded only.
The mechanical answer
Mutex<T> is the thread-safe analogue of RefCell: it provides exclusive mutable access to the inner value, but enforces exclusivity with a LOCK (atomic state + OS futex/semaphore) instead of a borrow flag, and it hands out a MutexGuard (RAII) that unlocks on drop. The key difference is in the auto-traits: RefCell<T> is !Sync (it's not safe to share across threads because the borrow flag is not atomic and a race would cause unsoundness), while Mutex<T> is Send + Sync when T is Send (the lock makes concurrent access safe). Cell<T> is also !Sync for the same reason. This is why shared mutable state across threads requires Mutex/RwLock (or atomics), and RefCell/Cell are strictly single-threaded. The compiler enforces this: you cannot share a RefCell across threads in safe code.
Trap
The naive answer is 'Mutex is just a thread-safe RefCell'. The locking mechanism (OS-level mutual exclusion) vs the borrow flag is a real semantic difference, and the Send/Sync auto-trait split is what the compiler enforces. Another trap: thinking RwLock is always faster than Mutex — under writer-heavy or contended workloads, RwLock's reader-writer overhead can make it slower; benchmark rather than assume. Candidates who connect the types to their auto-traits show real concurrency understanding.