On this page
Cell, RefCell, Mutex: Interior Mutability
Rust’s shared references (
&T) are immutable by default—Cell/RefCell/Mutex provide varying degrees of interior mutability under the premise of "sharing". Cell is suitable for Copy types (no runtime overhead), RefCell defers borrow checking to runtime (panic on error), and Mutex adds thread-safe locking (Send).
Origin of the Problem
let x = 5;
let r = &x; // &i32, immutable
// *r = 6; // ERROR: &T cannot be modified
Rust’s core rule: data cannot be modified through shared references (&T). This prevents data races and iterator invalidation. However, sometimes logically safe modifications are rejected by the rules—such as caching computed results or internal reference counting. Interior mutability allows you to modify internal state while holding &self.
Cell: Replacing the Entire Value (Copy Types Only)
use Cell;
let c = new;
c.set; // Replace internal value — no &mut self needed
let val = c.get; // Copy out
Cell does not return internal references—get() always copies the value (requires T: Copy), and set() always replaces the entire value. No references leak to the outside, so the borrow checker does not block it. Suitable for simple wrappers of i32, bool, enum, etc.
RefCell: Runtime Borrow Check
use RefCell;
let rc = new;
// borrow_mut guard dropped → borrow released
let borrow = rc.borrow; // runtime: OK
The rules are the same as the compile-time borrow checker—aliasing XOR mutation—but the check occurs at runtime. Violation → panic! (not a compile error). borrow() returns Ref<T> (Deref to &T), and borrow_mut() returns RefMut<T> (Deref to &mut T). These guards are dropped when they go out of scope, releasing the internal borrow count.
RefCell is not Sync—it cannot be shared across threads.
Mutex: Interior Mutability + Send + Locking
use Mutex;
let m = new;
let mut guard = m.lock.unwrap; // Block until lock is acquired
*guard += 1; // Access internal value via DerefMut
drop; // Release lock (or rely on scope exit)
Mutex combines RefCell's interior mutability + OS-level locking + Send guarantees. Under the hood, it uses UnsafeCell + an OS mutex (pthread_mutex_t on Linux). lock() blocks the current thread until the lock is acquired, and try_lock() is non-blocking, immediately returning Option<MutexGuard>.
Comparison of the Three
| Cell | RefCell | Mutex | |
|---|---|---|---|
| Interior Mutability | Y | Y | Y |
| Returns Reference | N (copy only) | Y (Ref/RefMut) | Y (MutexGuard) |
| Conflict Handling | N/A | panic | block |
| Thread-safe | N | N | Y |
| Check Timing | Compile-time (no borrow) | Runtime (panic) | Runtime (block) |
References
- Rust Book: Chapter 15.5
- Rustonomicon: UnsafeCell (the primitive behind all interior mutability)
Keywords: Cell, RefCell, Mutex, interior mutability, UnsafeCell, borrow_mut, runtime borrow check