In this lesson, you will learn how Cell<T> allows you to bypass Rust’s strict borrowing rules to mutate data even when you only hold an immutable reference to the container.
The Problem of Immutable Aliasing
In Rust, the borrow checker enforces a strict "alias XOR mutation" rule: you can have many immutable references (&T) or exactly one mutable reference (&mut T), but never both. However, there are scenarios—such as tracking statistics, caching, or modifying a state inside an otherwise shared structure—where you need to change data even when you only have an immutable handle to it.
How Cell Works
Cell<T> provides interior mutability for types that implement the Copy trait. Because the data inside a Cell is treated as a single, replaceable value, the compiler allows you to "swap" or "set" the interior value without needing a &mut reference to the Cell itself.
Unlike a Mutex (which we discussed in the context of thread-safe shared ownership), Cell performs no runtime locking. There are no semaphores or atomic operations involved; it is essentially a thin wrapper that informs the compiler that the data is allowed to change behind a shared reference.
A Practical Illustration
Consider a counter inside a struct. If you pass an immutable reference of this struct around your single-threaded program, you can still update the counter:
use std::cell::Cell;
struct Tracker {
hits: Cell<u32>,
}
fn increment(tracker: &Tracker) {
// We only have an immutable &Tracker, but we can still mutate 'hits'
let current = tracker.hits.get();
tracker.hits.set(current + 1);
}
By using Cell, we avoid the overhead of Mutex synchronization entirely. Since this is strictly for single-threaded code, Rust enforces safety by ensuring that Cell does not provide references to its interior data (via get returning a copy), preventing the common bugs associated with aliasing mutable memory.
Key Takeaway: Cell<T> enables internal mutation of data through immutable references in a single-threaded context by sacrificing the ability to get a reference to the interior value, thereby avoiding runtime synchronization costs.