Under The Hood
backend rust Rust 1.x (std::cell, std::sync)

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

react equivalent
N/A — Rust language 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.

before → after
Borrow checker rejection
let x = &v; // ❌ OLD: cannot mutate through a shared reference at compile time
RefCell runtime-checked mutation
let cell = RefCell::new(vec![1]);
cell.borrow_mut().push(2); // ✅ NEW: runtime-checked exclusive borrow

Interview 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.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.