When to use Cell in Rust?
When I first came across Cell in Rust, I understood what it did, but I struggled to understand why I would ever use it.
Suppose we want to keep track of how many times something has been called:
struct Stats {
calls: u32,
}
impl Stats {
fn record(&mut self) {
self.calls += 1;
}
}
fn main() {
let mut stats = Stats { calls: 0 };
stats.record();
stats.record();
println!("{}", stats.calls);
}$ cargo run -q
2Nothing unusual here. If we want to mutate something, we make it mutable.
So why would we ever write this?
use std::cell::Cell;
struct Stats {
calls: Cell<u32>,
}The answer becomes clearer when we stop thinking about Cell as an alternative to mut.
It is an alternative to requiring &mut self.
The problem with &mut self
Let’s change our example slightly:
struct Database {
query_count: u32,
}
impl Database {
fn get_user(&mut self, id: u64) {
self.query_count += 1;
// fetch user...
}
}get_user needs &mut self because it modifies query_count.
But conceptually, querying the database doesn’t really modify the database object. The counter is just internal bookkeeping.
Now imagine another function receives a shared reference to the database:
fn show_user(db: &Database) {
db.get_user(42);
}This doesn’t compile.
get_user requires &mut Database, but we only have &Database.
We could change everything to use mutable references:
fn show_user(db: &mut Database) {
db.get_user(42);
}But now a tiny implementation detail — a query counter — has changed the API of our entire object.
And &mut means something stronger than simply “I want to change something.”
It means:
I need exclusive access to this object.
There can only be one mutable reference to a value at a time. Rust allows either multiple shared references or one exclusive mutable reference.
That’s where Cell becomes useful.
Interior mutability
We can make only the counter mutable:
use std::cell::Cell;
struct Database {
query_count: Cell<u32>,
}
impl Database {
fn get_user(&self, id: u64) {
self.query_count
.set(self.query_count.get() + 1);
// fetch user...
}
}Notice that get_user now takes:
&selfinstead of:
&mut selfAnd this works:
fn show_user(db: &Database) {
db.get_user(42);
}The Database itself can remain shared. Only query_count is allowed to change.
This is called interior mutability: mutating something through a shared reference. Cell provides this safely by moving or copying values in and out instead of handing you a reference to the value inside.
For a Copy type such as u32, that looks like:
let count = self.query_count.get();
self.query_count.set(count + 1);get() gives us a copy of the current value, and set() replaces it with another one.
A real use case: Rc
A nice example of where this is actually useful is Rc.
Consider:
use std::rc::Rc;
let value = Rc::new(String::from("hello"));
let a = Rc::clone(&value);
let b = Rc::clone(&value);Every time an Rc is cloned, its reference count has to increase.
But look at the signature of Clone:
fn clone(&self) -> SelfIt receives &self, not &mut self.
So how can Rc modify its reference count?
Interior mutability.
Conceptually, the allocation behind an Rc contains something like:
struct RcInner<T> {
strong: Cell<usize>,
value: T,
}Cloning the Rc can then do something like:
self.strong.set(self.strong.get() + 1);even though it only has a shared reference.
The standard library uses this pattern for Rc’s reference counts.
This is the kind of situation where Cell starts to make sense.
The object needs to remain shareable, but a small piece of internal state still needs to change.
When should you use it?
Cell is particularly useful for small pieces of state:
Cell<bool>
Cell<u32>
Cell<usize>
Cell<Option<u32>>
Cell<MySmallEnum>Things like counters, flags, indexes, state markers, and bookkeeping values are good candidates.
For example:
struct Parser {
input: String,
position: Cell<usize>,
}or:
struct Widget {
dirty: Cell<bool>,
}or:
struct Connection {
requests: Cell<u64>,
}The common pattern is not simply “I need mutation.”
It is:
I only have
&self, but one small internal value still needs to change.
If you already naturally have &mut self, there is usually no reason to introduce Cell.
Just mutate the value normally.
Why not RefCell?
Cell and RefCell both provide interior mutability, but they work differently.
With Cell, you move or copy the entire value:
counter.set(10);
let value = counter.get();You don’t borrow the value inside.
A RefCell, on the other hand, lets you obtain references:
let mut value = data.borrow_mut();
value.push(42);Because of that, RefCell has to enforce Rust’s borrowing rules at runtime. Trying to create conflicting borrows causes a panic. Cell doesn’t have that runtime borrowing mechanism.
So for something tiny like:
Cell<u32>Cell is usually the simpler option.
For something you actually need to modify through a reference, such as:
RefCell<Vec<User>>RefCell makes more sense.
Both are intended for single-threaded shared mutation; Cell itself is not Sync.
The mental model
The mistake I initially made was comparing:
Cell<T>with:
mut TBut that’s not really the interesting comparison.
The interesting comparison is:
fn method(&mut self)versus:
fn method(&self)If &mut self accurately represents what your operation does, use it.
But occasionally an operation is logically read-only while some internal bookkeeping still needs to change.
That’s where Cell fits.
It lets the object remain shared while giving one small field permission to change.