Expand description
Shareable mutable containers.
Rust memory safety is based on this rule: Given an objectT, it is only possible tohave one of the following:
- Several immutable references (
&T) to the object (also known asaliasing). - One mutable reference (
&mut T) to the object (also known asmutability).
This is enforced by the Rust compiler. However, there are situations where this rule is notflexible enough. Sometimes it is required to have multiple references to an object and yetmutate it.
Shareable mutable containers exist to permit mutability in a controlled manner, even in thepresence of aliasing.Cell<T>,RefCell<T>, andOnceCell<T> allow doing this ina single-threaded way—they do not implementSync. (If you need to do aliasing andmutation among multiple threads,Mutex<T>,RwLock<T>,OnceLock<T> oratomictypes are the correct data structures to do so).
Values of theCell<T>,RefCell<T>, andOnceCell<T> types may be mutated through sharedreferences (i.e. the common&T type), whereas most Rust types can only be mutated throughunique (&mut T) references. We say these cell types provide ‘interior mutability’(mutable via&T), in contrast with typical Rust types that exhibit ‘inherited mutability’(mutable only via&mut T).
Cell types come in four flavors:Cell<T>,RefCell<T>,OnceCell<T>, andLazyCell<T>.Each provides a different way of providing safe interior mutability.
§Cell<T>
Cell<T> implements interior mutability by moving values in and out of the cell. That is, an&mut T to the inner value can never be obtained, and the value itself cannot be directlyobtained without replacing it with something else. Both of these rules ensure that there isnever more than one reference pointing to the inner value. This type provides the followingmethods:
- For types that implement
Copy, thegetmethod retrieves the currentinterior value by duplicating it. - For types that implement
Default, thetakemethod replaces the currentinterior value withDefault::default()and returns the replaced value. - All types have:
replace: replaces the current interior value and returns the replacedvalue.into_inner: this method consumes theCell<T>and returns theinterior value.set: this method replaces the interior value, dropping the replaced value.
Cell<T> is typically used for more simple types where copying or moving values isn’t tooresource intensive (e.g. numbers), and should usually be preferred over other cell types whenpossible. For larger and non-copy types,RefCell provides some advantages.
§RefCell<T>
RefCell<T> uses Rust’s lifetimes to implement “dynamic borrowing”, a process whereby one canclaim temporary, exclusive, mutable access to the inner value. Borrows forRefCell<T>s aretracked atruntime, unlike Rust’s native reference types which are entirely trackedstatically, at compile time.
An immutable reference to aRefCell’s inner value (&T) can be obtained withborrow, and a mutable borrow (&mut T) can be obtained withborrow_mut. When these functions are called, they first verify thatRust’s borrow rules will be satisfied: any number of immutable borrows are allowed or asingle mutable borrow is allowed, but never both. If a borrow is attempted that would violatethese rules, the thread will panic.
The correspondingSync version ofRefCell<T> isRwLock<T>.
§OnceCell<T>
OnceCell<T> is somewhat of a hybrid ofCell andRefCell that works for values thattypically only need to be set once. This means that a reference&T can be obtained withoutmoving or copying the inner value (unlikeCell) but also without runtime checks (unlikeRefCell). However, its value can also not be updated once set unless you have a mutablereference to theOnceCell.
OnceCell provides the following methods:
get: obtain a reference to the inner valueset: set the inner value if it is unset (returns aResult)get_or_init: return the inner value, initializing it if neededget_mut: provide a mutable reference to the inner value, only availableif you have a mutable reference to the cell itself.
The correspondingSync version ofOnceCell<T> isOnceLock<T>.
§LazyCell<T, F>
A common pattern with OnceCell is, for a given OnceCell, to use the same function on everycall toOnceCell::get_or_init with that cell. This is what is offered byLazyCell,which pairs cells ofT with functions ofF, and always callsF before it yields&T.This happens implicitly by simply attempting to dereference the LazyCell to get its contents,so its use is much more transparent with a place which has been initialized by a constant.
More complicated patterns that don’t fit this description can be built onOnceCell<T> instead.
LazyCell works by providing an implementation ofimpl Deref that calls the function,so you can just use it by dereference (e.g.*lazy_cell orlazy_cell.deref()).
The correspondingSync version ofLazyCell<T, F> isLazyLock<T, F>.
§When to choose interior mutability
The more common inherited mutability, where one must have unique access to mutate a value, isone of the key language elements that enables Rust to reason strongly about pointer aliasing,statically preventing crash bugs. Because of that, inherited mutability is preferred, andinterior mutability is something of a last resort. Since cell types enable mutation where itwould otherwise be disallowed though, there are occasions when interior mutability might beappropriate, or evenmust be used, e.g.
- Introducing mutability ‘inside’ of something immutable
- Implementation details of logically-immutable methods.
- Mutating implementations of
Clone.
§Introducing mutability ‘inside’ of something immutable
Many shared smart pointer types, includingRc<T> andArc<T>, provide containers that canbe cloned and shared between multiple parties. Because the contained values may bemultiply-aliased, they can only be borrowed with&, not&mut. Without cells it would beimpossible to mutate data inside of these smart pointers at all.
It’s very common then to put aRefCell<T> inside shared pointer types to reintroducemutability:
usestd::cell::{RefCell, RefMut};usestd::collections::HashMap;usestd::rc::Rc;fnmain() {letshared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));// Create a new block to limit the scope of the dynamic borrow{letmutmap: RefMut<'_,_> = shared_map.borrow_mut(); map.insert("africa",92388); map.insert("kyoto",11837); map.insert("piccadilly",11826); map.insert("marbles",38); }// Note that if we had not let the previous borrow of the cache fall out // of scope then the subsequent borrow would cause a dynamic thread panic. // This is the major hazard of using `RefCell`.lettotal: i32 = shared_map.borrow().values().sum();println!("{total}");}Note that this example usesRc<T> and notArc<T>.RefCell<T>s are for single-threadedscenarios. Consider usingRwLock<T> orMutex<T> if you need shared mutability in amulti-threaded situation.
§Implementation details of logically-immutable methods
Occasionally it may be desirable not to expose in an API that there is mutation happening“under the hood”. This may be because logically the operation is immutable, but e.g., cachingforces the implementation to perform mutation; or because you must employ mutation to implementa trait method that was originally defined to take&self.
usestd::cell::OnceCell;structGraph { edges: Vec<(i32, i32)>, span_tree_cache: OnceCell<Vec<(i32, i32)>>}implGraph {fnminimum_spanning_tree(&self) -> Vec<(i32, i32)> {self.span_tree_cache .get_or_init(||self.calc_span_tree()) .clone() }fncalc_span_tree(&self) -> Vec<(i32, i32)> {// Expensive computation goes herevec![] }}§Mutating implementations ofClone
This is simply a special - but common - case of the previous: hiding mutability for operationsthat appear to be immutable. Theclone method is expected to not change thesource value, and is declared to take&self, not&mut self. Therefore, any mutation thathappens in theclone method must use cell types. For example,Rc<T> maintains itsreference counts within aCell<T>.
usestd::cell::Cell;usestd::ptr::NonNull;usestd::process::abort;usestd::marker::PhantomData;structRc<T:?Sized> { ptr: NonNull<RcInner<T>>, phantom: PhantomData<RcInner<T>>,}structRcInner<T:?Sized> { strong: Cell<usize>, refcount: Cell<usize>, value: T,}impl<T:?Sized> CloneforRc<T> {fnclone(&self) -> Rc<T> {self.inc_strong(); Rc { ptr:self.ptr, phantom: PhantomData, } }}traitRcInnerPtr<T:?Sized> {fninner(&self) ->&RcInner<T>;fnstrong(&self) -> usize {self.inner().strong.get() }fninc_strong(&self) {self.inner() .strong .set(self.strong() .checked_add(1) .unwrap_or_else(|| abort() )); }}impl<T:?Sized> RcInnerPtr<T>forRc<T> {fninner(&self) ->&RcInner<T> {unsafe{self.ptr.as_ref() } }}Structs§
- Borrow
Error - An error returned by
RefCell::try_borrow. - Borrow
MutError - An error returned by
RefCell::try_borrow_mut. - Cell
- A mutable memory location.
- Lazy
Cell - A value which is initialized on the first access.
- Once
Cell - A cell which can nominally be written to only once.
- Ref
- Wraps a borrowed reference to a value in a
RefCellbox.A wrapper type for an immutably borrowed value from aRefCell<T>. - RefCell
- A mutable memory location with dynamically checked borrow rules
- RefMut
- A wrapper type for a mutably borrowed value from a
RefCell<T>. - Unsafe
Cell - The core primitive for interior mutability in Rust.
- Sync
Unsafe Cell Experimental UnsafeCell, butSync.
Traits§
- Clone
From Cell Experimental - Types for which cloning
Cell<Self>is sound.