Interior Mutability

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>() and align_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 writing T directly.
  • 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]