Deep-Dive System Documentation
Interior Mutability: Bypassing Compile-Time Borrowing Rules
Rust's borrow checker enforces a strict rule: you can have either one mutable reference (&mut T) OR any number of immutable references (&T) to a value, but never both at the same time. Interior mutability is a design pattern that lets you mutate a value inside an immutable container, shifting borrow checking from compile-time to runtime.
Cell<T> vs RefCell<T>
| Feature | Cell<T> | RefCell<T> |
|---|---|---|
| Mutation Style | Replaces the value completely (no references) | Borrow references dynamically at runtime |
| Overhead | Zero runtime overhead (compiler optimized copy) | Tiny overhead (keeps track of borrows dynamically) |
| Restrictions | Requires Copy or taking ownership of value | Works with any non-Copy or complex type |
Using Cell<T> (Zero Overhead)
Cell works by moving values in and out of the container, rather than giving you direct references.
use std::cell::Cell;
let count = Cell::new(42);
count.set(50); // Mutates inside an immutable container
println!("Value: {}", count.get()); // 50
Using RefCell<T> (Runtime Borrow Checking)
RefCell tracks borrow flags at runtime. You get dynamically checked references using .borrow() and .borrow_mut().
use std::cell::RefCell;
let shared = RefCell::new(String::from("Rust"));
{
// Shared immutable borrows
let b1 = shared.borrow();
let b2 = shared.borrow();
println!("Borrows: {} & {}", b1, b2);
} // b1 and b2 dropped here
{
// Mutable borrow
let mut b_mut = shared.borrow_mut();
b_mut.push_str(" Cheat Sheet");
} // b_mut dropped here
Runtime Panic (The Danger)
If you violate borrowing rules with RefCell at runtime, the code compiles successfully, but panics at runtime:
let data = RefCell::new(5);
let b1 = data.borrow();
let mut b2 = data.borrow_mut(); // 💥 PANIC: already borrowed!
- Safe Check: Use
.try_borrow()and.try_borrow_mut()to return aResultinstead of panicking.
Thread Safety
Neither Cell nor RefCell are thread-safe. They do not implement Sync, preventing data races from happening across threads. For thread-safe interior mutability, use Mutex<T> or RwLock<T>.
Useful Methods: Cell
Cell::new(value: T) -> Cell<T>
Creates a new Cell containing the given value.
use std::cell::Cell;
let my_cell = Cell::new(42);
.get(&self) -> T
Returns a copy of the contained value. Only available if T implements the Copy trait.
use std::cell::Cell;
let my_cell = Cell::new(10);
assert_eq!(my_cell.get(), 10);
Useful Methods: RefCell
.borrow(&self) -> Ref<'_, T>
Immutably borrows the wrapped value. Multiple immutable borrows can be active at the same time. Panics if the value is currently mutably borrowed.
use std::cell::RefCell;
let c = RefCell::new(5);
let r1 = c.borrow();
let r2 = c.borrow();
assert_eq!(*r1, 5);
.borrow_mut(&self) -> RefMut<'_, T>
Mutably borrows the wrapped value. Only one mutable borrow can be active, and no other borrows may exist. Panics if the value is currently borrowed.
use std::cell::RefCell;
let c = RefCell::new(5);
*c.borrow_mut() += 10;
assert_eq!(*c.borrow(), 15);
Quick Reference Guide
use std::cell::{Cell, RefCell};
let c = Cell::new(10);
c.set(20);
let r = RefCell::new(String::from('A'));
let mut ref_mut = r.borrow_mut();
ref_mut.push('B');