19.6 Interior Mutability with Cell<T>
, RefCell<T>
, and OnceCell<T>
Rust’s compile-time guarantees normally prohibit mutating data through an immutable reference. This is essential for safety but can occasionally be too restrictive when you know a certain mutation is safe.
Interior mutability provides a solution by allowing controlled mutation at runtime, guarded by checks or specialized mechanisms. The most common types for this purpose are:
Cell<T>
RefCell<T>
OnceCell<T>
(with a corresponding thread-safe version instd::sync
)
19.6.1 Cell<T>
: Copy-Based Interior Mutability
Cell<T>
replaces values rather than borrowing them. It works only for types that implement Copy
. There are no runtime borrow checks; you can simply set or get the stored value.
Example:
use std::cell::Cell; fn main() { let cell = Cell::new(42); cell.set(100); cell.set(1000); println!("Value: {}", cell.get()); }
19.6.2 RefCell<T>
: Runtime Borrow Checking
For non-Copy
types or more complex borrowing patterns, RefCell<T>
enforces borrow rules at runtime. If you violate Rust’s normal borrowing constraints (e.g., attempting to borrow mutably while another borrow exists), your program will panic.
Example:
use std::cell::RefCell; fn main() { let cell = RefCell::new(42); { *cell.borrow_mut() += 1; println!("Value: {}", cell.borrow()); } { let mut bm = cell.borrow_mut(); *bm += 1; // println!("Value: {}", cell.borrow()); // This would panic at runtime } }
19.6.3 Combining Rc<T>
and RefCell<T>
A common pattern is Rc<RefCell<T>>
: multiple owners of data that requires mutation. This is particularly valuable in graph or tree structures with dynamic updates:
use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>, } fn main() { let root = Rc::new(RefCell::new(Node { value: 1, children: vec![] })); let child1 = Rc::new(RefCell::new(Node { value: 2, children: vec![] })); let child2 = Rc::new(RefCell::new(Node { value: 3, children: vec![] })); root.borrow_mut().children.push(Rc::clone(&child1)); root.borrow_mut().children.push(Rc::clone(&child2)); child1.borrow_mut().value = 42; println!("{:#?}", root); }
19.6.4 OnceCell<T>
: Single Initialization
OnceCell<T>
allows initializing data exactly once, then accessing it immutably afterward. A thread-safe variant (std::sync::OnceCell
) is available for concurrent scenarios.
Example:
use std::cell::OnceCell; fn main() { let cell = OnceCell::new(); cell.set(42).unwrap(); println!("Value: {}", cell.get().unwrap()); // Attempting to set a second time would panic }
Summary of Interior Mutability Tools
Cell<T>
: ForCopy
types only, provides set/get operations without borrow checking.RefCell<T>
: For complex mutation needs with runtime borrow checking.OnceCell<T>
: Allows a single initialization followed by immutable reads.Rc<RefCell<T>>
: Frequently used for shared, mutable data in single-threaded contexts.