Interior Mutability
get() and set()
Cell::get and Cell::set are the primary read and write methods on std::cell::Cell<T>, reading a bitwise copy of the inner value and overwriting the inner value in place through a shared reference &Cell<T>.
Method Signatures and Trait Bounds
impl<T: Copy> Cell<T> {
pub const fn get(&self) -> T;
}
impl<T> Cell<T> {
pub const fn set(&self, val: T);
pub fn update(&self, f: impl FnOnce(T) -> T) -> T
where
T: Copy;
}
| Method | Bound on T |
Behavior |
|---|---|---|
cell.get() |
T: Copy |
Reads and returns a bitwise copy of the contained value |
cell.set(val) |
Any T (even !Copy) |
Replaces the contained value with val and drops the old value |
cell.update(f) |
T: Copy |
Computes let next = f(self.get()); self.set(next); next |
Why get() Requires T: Copy While set() Accepts Any T
get(&self) -> T: ReturningTby value from behind&selfwithout emptying theCell<T>duplicates the value. Duplicating a value in Rust without calling user-defined code (Clone::clone) is only sound whenT: Copy(for example,i32,f64,bool,(u32, u32), or&str). IfCell<String>::get()were allowed to bitwise-copy aString, twoStringinstances would own and double-free the same heap buffer.set(&self, val: T): Overwriting the existing value movesvalinto theUnsafeCell<T>and drops the displaced old value viamem::replace. No duplication occurs, soset()works for anyT, includingStringandVec<u8>.
// ❌ does not compile
use std::cell::Cell;
fn main() {
let text_cell = Cell::new(String::from("hello"));
// `set` works on `Cell<String>` because it overwrites and drops the old String:
text_cell.set(String::from("world"));
// `get` fails to compile because `String` does not implement `Copy`:
let s = text_cell.get();
}
error[E0599]: the method `get` exists for struct `Cell<String>`, but its trait bounds were not satisfied
--> src/main.rs:9:23
|
9 | let s = text_cell.get();
| ^^^ method cannot be called on `Cell<String>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`String: Copy`
Idiomatic Usage of get(), set(), and update()
use std::cell::Cell;
#[derive(Debug, Clone, Copy)]
enum ConnectionState {
Disconnected,
Connecting { attempt: u8 },
Connected,
}
struct Client {
state: Cell<ConnectionState>,
bytes_sent: Cell<u64>,
}
impl Client {
fn new() -> Self {
Self {
state: Cell::new(ConnectionState::Disconnected),
bytes_sent: Cell::new(0),
}
}
fn connect(&self) {
self.state.set(ConnectionState::Connecting { attempt: 1 });
self.state.set(ConnectionState::Connected);
}
fn transmit(&self, bytes: u64) -> u64 {
self.bytes_sent.update(|prev| prev + bytes)
}
}
fn main() {
let client = Client::new();
println!("initial state: {:?}", client.state.get());
client.connect();
let total1 = client.transmit(256);
let total2 = client.transmit(512);
println!("current state: {:?}", client.state.get());
println!("totals: {} -> {}", total1, total2);
}
initial state: Disconnected
current state: Connected
totals: 256 -> 768
Drop Timing Inside Cell::set
When cell.set(new_val) is called on a Cell<T> where T has a destructor, set first moves new_val into the Cell and then drops the displaced old value. This ordering ensures that even if the old value's Drop implementation inspects the Cell, the Cell already holds the newly installed value rather than a half-dropped state.