One of Rust's primary goals is to prevent the mutation of immutable data and avoid data races at compile time. Under normal circumstances, Rust does not allow changing data through an immutable reference. However, in systems with caching, lazy computations, or logic that requires modifying internal state through a reference, this can become necessary. This is where the interior mutability pattern comes into play.
Without interior mutability, implementing caches, lazy initialization, and many other idioms can be difficult or impossible while maintaining safe ownership and references. A classic example is a cache inside a function that provides the outside world with only an immutable reference of itself.
Rust has special types — Cell and RefCell (and their multi-threaded counterparts) that allow modifying the internal value even through an immutable reference, controlling safety at runtime rather than compile time.
Cell<T> is a copy primitive that allows modifying or reading a value without using references, but only for types that implement Copy.RefCell<T> allows obtaining a mutable reference "on the fly" even with only an immutable external reference, but if an attempt is made to obtain two mutable references or one mutable and several immutable references simultaneously, it will panic at runtime.Code example:
use std::cell::RefCell; struct Foo { cache: RefCell<Option<u32>>, } impl Foo { fn get_or_compute(&self) -> u32 { if let Some(val) = *self.cache.borrow() { return val; } let computed = 42; *self.cache.borrow_mut() = Some(computed); computed } }
Key features:
Can we safely use RefCell for multi-threaded structures?
No, RefCell is not thread-safe. For multi-threaded environments, use Mutex or RwLock.
Can a reference to the contents of Cell<T> be returned?
No, Cell does not yield any references, only copies or updates the value. It only works with Copy types; for all others, use RefCell.
What happens if borrow_mut is called twice in a row on the same RefCell?
It will cause a panic at runtime, as RefCell tracks the number of active references. The second attempt to gain mutable access while an existing reference is present will lead to a panic.
In a project, RefCell is always used to store any mutable state within a structure even when mutable ownership could be utilized. The code becomes verbose, runtime panics occur, and testing becomes challenging.
Pros: Can quickly bypass compiler restrictions to achieve the desired behavior.
Cons: High risk of errors at runtime, application crashes, difficult debugging of logic.
RefCell is used only for implementing lazy caches in large structures, while for everything else, classic ownership and references are maintained. All values are cleared correctly, no panics occur.
Pros: Transparent logic, minimal use of interior mutability, the code is robust and predictable.
Cons: Requires careful attention to the boundaries of data modification: reading the cache is always safe, writing should only occur as necessary and in narrowly defined places.