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: Returning T by value from behind &self without emptying the Cell<T> duplicates the value. Duplicating a value in Rust without calling user-defined code (Clone::clone) is only sound when T: Copy (for example, i32, f64, bool, (u32, u32), or &str). If Cell<String>::get() were allowed to bitwise-copy a String, two String instances would own and double-free the same heap buffer.
  • set(&self, val: T): Overwriting the existing value moves val into the UnsafeCell<T> and drops the displaced old value via mem::replace. No duplication occurs, so set() works for any T, including String and Vec<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.