Cell<T>
std::cell::Cell<T> is a single-threaded interior mutability container that allows mutating its inner value through &Cell<T> by moving values in and out (or copying them when T: Copy) without ever handing out a reference &T or &mut T to the interior while shared.
Memory Layout and Zero-Cost Guarantee
Cell<T> is defined as a #[repr(transparent)] wrapper around UnsafeCell<T>, which in turn is #[repr(transparent)] around T:
Memory Layout of `Cell<u64>` (8 bytes, align 8 — 0 bytes overhead):
+-------------------------------------------------------------------+
| Cell<u64> -> UnsafeCell<u64> -> u64 (8 bytes) |
+-------------------------------------------------------------------+
Because Cell<T> stores no borrow counter, lock word, or discriminant tag:
size_of::<Cell<T>>() == size_of::<T>()andalign_of::<Cell<T>>() == align_of::<T>().- Reading and writing a
Cell<T>compiles to the exact same machine load and store instructions as reading and writingTdirectly. - Operations on
Cell<T>can never panic or deadlock at runtime.
use std::cell::Cell;
struct Point {
x: Cell<i32>,
y: Cell<i32>,
}
fn main() {
println!("size of i32: {} bytes", size_of::<i32>());
println!("size of Cell<i32>: {} bytes", size_of::<Cell<i32>>());
println!("size of Point: {} bytes", size_of::<Point>());
let p = Point {
x: Cell::new(10),
y: Cell::new(20),
};
p.x.set(15);
p.y.set(p.y.get() + 5);
println!("point = ({}, {})", p.x.get(), p.y.get());
}
size of i32: 4 bytes
size of Cell<i32>: 4 bytes
size of Point: 8 bytes
point = (15, 25)
Why Cell<T> Forbids &T Borrowing
If Cell<T> allowed borrowing an inner &T while shared, calling .set(new_val) on the same Cell<T> while that &T was still alive would overwrite the memory behind an active reference—violating Rust's aliasing rules and causing undefined behavior (such as dangling pointers if T were an enum whose variant changed from Some(String) to None).
By restricting shared access to whole-value replacement (.set(), .replace(), .swap(), .take()) and bitwise copies (.get() when T: Copy), no inner reference ever coexists with .set().
Direct Exclusive Access via Cell::get_mut
When the caller holds a unique mutable reference &mut Cell<T> (for example, through &mut self), calling .get_mut() returns &mut T directly because compile-time exclusivity guarantees no other &Cell<T> alias exists:
use std::cell::Cell;
fn main() {
let mut cell = Cell::new(vec![1, 2, 3]);
// Because `cell` is uniquely borrowed via `&mut`, `.get_mut()` returns `&mut Vec<i32>`:
cell.get_mut().push(4);
println!("vec inside cell: {:?}", cell.into_inner());
}
vec inside cell: [1, 2, 3, 4]
Converting &mut T and &mut [T] into Cell Views
Cell::from_mut(&mut val) converts an exclusive reference &mut T into &Cell<T> for the duration of the borrow. When T is a slice [U], chaining .as_slice_of_cells() converts &Cell<[U]> into &[Cell<U>], allowing multiple index positions of a slice to be mutated through shared references simultaneously:
use std::cell::Cell;
fn compact_zeros(slice: &mut [i32]) {
let cells: &[Cell<i32>] = Cell::from_mut(slice).as_slice_of_cells();
let mut write_idx = 0;
for read_idx in 0..cells.len() {
let val = cells[read_idx].get();
if val != 0 {
cells[read_idx].set(0);
cells[write_idx].set(val);
write_idx += 1;
}
}
}
fn main() {
let mut numbers = [0, 4, 0, 7, 9, 0, 2];
compact_zeros(&mut numbers);
println!("compacted: {:?}", numbers);
}
compacted: [4, 7, 9, 2, 0, 0, 0]