Cell
Cell<T> is an interior-mutability container for value replacement: it lets shared references get, set, take, or replace the inner value without handing out references to it.
What it is
Cell<T> is the simplest standard-library interior-mutability type.
It is best for small values, flags, counters, and options where mutation can be expressed as copying, taking, or replacing the whole value.
Unlike RefCell, Cell<T> does not track borrows at runtime and does not return Ref guards.
Its safety comes from not exposing normal references to the inner T.
How it works
For T: Copy, get() copies the value out.
For any T, set(value) replaces the inner value and drops the old one, replace(value) returns the old value, and take() replaces the value with Default::default().
into_inner() consumes the Cell<T> and returns the inner value without replacement.
These APIs are value-oriented: the caller never receives a long-lived borrow into the cell.
Because no &T or &mut T to the inner value is exposed by the core API, there can be no aliasing violation through those references.
This makes Cell<T> cheap and predictable for local, single-threaded interior state.
It has no borrow counter and no panic path for overlapping borrows, unlike RefCell.
Cell<T> is not a synchronization primitive.
For cross-thread shared mutation, use Atomics for supported scalar operations or Shared State with Mutex for general data.
Example
use std::cell::Cell;
struct ParserState {
line: Cell<usize>,
had_error: Cell<bool>,
}
impl ParserState {
fn advance(&self) {
self.line.set(self.line.get() + 1);
}
fn mark_error(&self) {
self.had_error.set(true);
}
}
fn main() {
let state = ParserState {
line: Cell::new(1),
had_error: Cell::new(false),
};
state.advance();
state.mark_error();
assert_eq!(state.line.get(), 2);
assert!(state.had_error.get());
}Worked example: taking a non-Copy value
use std::cell::Cell;
struct OneShot {
message: Cell<Option<String>>,
}
impl OneShot {
fn take(&self) -> Option<String> {
self.message.take()
}
}
fn main() {
let slot = OneShot {
message: Cell::new(Some(String::from("ready"))),
};
assert_eq!(slot.take().as_deref(), Some("ready"));
assert_eq!(slot.take(), None);
}Cell<Option<T>> is a common way to move a value out through &self exactly once without exposing an interior reference.
Common errors
Calling get() on a non-Copy cell produces a trait-bound error because get must copy the value out:
error[E0599]: the method `get` exists for struct `Cell<String>`, but its trait bounds were not satisfiedUse replace, take, or into_inner for non-Copy values, or use RefCell if callers need borrowed access to the value in place.
Best practice
- ✅ Use
Cell<T>forCopycounters, booleans, enum states, and small handles mutated through&self. - ✅ Use
replaceortakewhenTis notCopyand whole-value movement is the right operation. - ✅ Prefer RefCell when callers need temporary borrowed access to a non-
Copyinner value. - ✅ Keep
Cellfields private so methods preserve the type’s invariants. - ✅ Use
Cell<Option<T>>for one-shot initialization handoff, delayed extraction, or “take once” fields in single-threaded code. - ✅ Prefer atomics over
Cellfor counters that may be touched from multiple threads.
Pitfalls
- ⚠️
Cell<T>cannot give you&Tor&mut Tto arbitrary inner data. - ⚠️ Replacing a large value repeatedly can be more expensive than borrowing it mutably through RefCell.
- ⚠️
Cell<T>is for single-threaded interior mutability; do not use it as a thread-safe counter. - ⚠️ Widespread public
Cellfields can make mutation paths hard to audit. - ⚠️
Cellmakes every write a whole-value operation; for collections, that often means moving or replacing the whole collection instead of editing one element.
See also
Interior Mutability · RefCell · Copy and Clone · Ownership · Borrowing · Atomics · Shared State with Mutex · Smart Pointers & Interior Mutability
Sources
- Standard library,
std::cell::Cell- std, https://doc.rust-lang.org/std/cell/struct.Cell.html - Standard library,
std::cellmodule - std, https://doc.rust-lang.org/std/cell/index.html
