Cell<T>
std::cell::Cell<T> is a zero-overhead single-threaded interior mutability container that enables mutating an inner value through a shared reference (&Cell<T>) by moving or copying values in and out rather than handing out references to the inner data.
Why Cell<T> Cannot Produce Dangling References
Because no safe method on &Cell<T> ever returns a reference &T or &mut T pointing inside the cell, there is never an active borrow that could be invalidated by a concurrent write on the same thread. As a result, Cell<T> needs no runtime borrow counter, has the exact same size and alignment as T (#[repr(transparent)] over UnsafeCell<T>), and can never panic at runtime.
&Cell<T> Operations (No inner &T reference ever escapes):
set(new_val) / replace(new_val)
|
v
+------------------+
| Cell<T> |
| inner value T |
+------------------+
|
v
get() (if T: Copy) / take() (if T: Default)
Core Cell<T> Methods by Trait Bound
Cell<T> works with any type T (including non-Copy types like String or Vec<T>), with additional convenience methods unlocked when T: Copy or T: Default:
| Method | Required Bound on T |
Signature | Behavior |
|---|---|---|---|
Cell::new(val) |
Any T |
const fn new(value: T) -> Cell<T> |
Creates a new Cell<T> |
.set(val) |
Any T |
fn set(&self, val: T) |
Replaces and drops the old value |
.replace(val) |
Any T |
fn replace(&self, val: T) -> T |
Swaps in val and returns the previous value |
.swap(&other) |
Any T |
fn swap(&self, other: &Cell<T>) |
Swaps contents of two Cell<T> instances |
.into_inner() |
Any T (consumes Cell) |
fn into_inner(self) -> T |
Unwraps the Cell and returns T |
.get_mut() |
Any T (requires &mut Cell) |
fn get_mut(&mut self) -> &mut T |
Zero-cost mutable access when exclusive &mut is held |
.get() |
T: Copy |
fn get(&self) -> T |
Returns a bitwise copy of the current value |
.update(f) |
T: Copy |
fn update(&self, f: impl FnOnce(T) -> T) -> T |
Reads copy, applies f, writes result, and returns new value |
.take() |
T: Default |
fn take(&self) -> T |
Moves out the current value and leaves T::default() in its place |
Mutating Copy and Non-Copy Fields Through &self
use std::cell::Cell;
struct ClientSession {
request_count: Cell<u32>,
last_token: Cell<String>,
}
impl ClientSession {
fn record_request(&self, new_token: String) -> String {
// `u32` is `Copy`, so `.get()` and `.set()` work directly:
self.request_count.set(self.request_count.get() + 1);
// `String` is not `Copy`, but `.replace()` moves the old String out
// and moves `new_token` in without any borrow checks:
self.last_token.replace(new_token)
}
}
fn main() {
let session = ClientSession {
request_count: Cell::new(0),
last_token: Cell::new(String::from("init-token")),
};
let prev1 = session.record_request(String::from("token-alpha"));
let prev2 = session.record_request(String::from("token-beta"));
println!("requests: {}", session.request_count.get());
println!("prev1: {}", prev1);
println!("prev2: {}", prev2);
println!("current: {}", session.last_token.take());
println!("after take: {:?}", session.last_token.into_inner());
}
requests: 2
prev1: init-token
prev2: token-alpha
current: token-beta
after take: ""
Slice Projection with Cell::from_mut and Cell::as_slice_of_cells
A unique mutable reference &mut [T] can be converted into &Cell<[T]> via Cell::from_mut, and then projected into &[Cell<T>] via .as_slice_of_cells(). This enables in-place mutation of multiple elements of a slice through shared references without index borrow conflicts:
use std::cell::Cell;
fn main() {
let mut data = [10, 20, 30, 40];
let cell_slice: &[Cell<i32>] = Cell::from_mut(&mut data[..]).as_slice_of_cells();
cell_slice[0].set(cell_slice[0].get() + cell_slice[3].get());
cell_slice[1].swap(&cell_slice[2]);
println!("modified data: {:?}", data);
}
modified data: [50, 30, 20, 40]