Rust's ownership and borrowing rules are enforced at compile time, ensuring memory safety without a garbage collector. However, there are situations where you need to mutate data even when there are immutable references to it. This pattern is called interior mutability.
The standard library provides two key types for interior mutability:
Cell<T>: Provides interior mutability for Copy types. It moves values in and out rather than borrowing, making it zero-cost but limited to types that implement Copy.RefCell<T>: Provides interior mutability for any type by tracking borrows at runtime. It enforces borrowing rules dynamically and will panic if you violate them (e.g., two mutable borrows at once).Use Cell<T> when:
Copy types (integers, booleans, small structs)Use RefCell<T> when:
Copy types (String, Vec, etc.)Cell<T>:
Cell::new(value) - Create a new Cellcell.get() - Get a copy of the value (requires T: Copy)cell.set(value) - Set a new valuecell.replace(value) - Set value and return old valuecell.take() - Take the value, leaving Default::default() (requires T: Default)RefCell<T>:
RefCell::new(value) - Create a new RefCellrefcell.borrow() - Get immutable reference (Ref<T>), panics if mutably borrowedrefcell.borrow_mut() - Get mutable reference (RefMut<T>), panics if any borrows existrefcell.try_borrow() - Returns Result<Ref<T>, BorrowError>refcell.try_borrow_mut() - Returns Result<RefMut<T>, BorrowMutError>Implement the following types and functions to demonstrate interior mutability patterns:
Counter - A simple counter using Cell:
new() -> Self - Create counter with value 0get(&self) -> i32 - Get current countset(&self, value: i32) - Set the countincrement(&self) - Increment by 1decrement(&self) - Decrement by 1CachedValue<T: Copy + Default> - A value that tracks access count:
new(value: T) -> Self - Create with initial valueget(&self) -> T - Get value (increments access count)set(&self, value: T) - Set value (resets access count)access_count(&self) -> u32 - Get number of times value was accessedSharedString - A string that can be modified through shared references:
new(s: &str) -> Self - Create with initial stringget(&self) -> String - Get a clone of the stringset(&self, s: &str) - Set a new stringappend(&self, s: &str) - Append to the stringlen(&self) -> usize - Get string lengthis_empty(&self) -> bool - Check if emptySharedVec<T> - A vector with interior mutability:
new() -> Self - Create empty vectorpush(&self, value: T) - Add elementpop(&self) -> Option<T> - Remove and return last elementlen(&self) -> usize - Get lengthis_empty(&self) -> bool - Check if emptyget(&self, index: usize) -> Option<T> where T: Clone - Get element at indexSafeCell<T> - A RefCell wrapper with safe borrowing:
new(value: T) -> Self - Create with initial valuetry_read(&self) -> Option<std::cell::Ref<T>> - Try to borrow immutablytry_write(&self) -> Option<std::cell::RefMut<T>> - Try to borrow mutablyis_borrowed(&self) -> bool - Check if currently borrowed (any type)with_value<F, R>(&self, f: F) -> Option<R> where F: FnOnce(&T) -> R - Apply function if not borrowedwith_value_mut<F, R>(&self, f: F) -> Option<R> where F: FnOnce(&mut T) -> R - Apply mutating functionSharedCounter - A reference-counted counter with interior mutability:
new() -> Rc<Self> - Create new counter wrapped in Rcget(&self) -> i32 - Get current valueincrement(&self) - Increment by 1decrement(&self) - Decrement by 1add(&self, n: i32) - Add n to counterTreeNode<T> - A tree node where children can be added through shared references:
new(value: T) -> Rc<Self> - Create leaf nodevalue(&self) -> std::cell::Ref<T> - Get reference to valueset_value(&self, value: T) - Set new valueadd_child(&self, child: Rc<TreeNode<T>>) - Add a child nodechildren_count(&self) -> usize - Get number of childrenuse cell_refcell::*;
use std::rc::Rc;
// Cell-based Counter
let counter = Counter::new();
counter.increment();
counter.increment();
assert_eq!(counter.get(), 2);
// CachedValue with access tracking
let cached = CachedValue::new(42);
assert_eq!(cached.get(), 42);
assert_eq!(cached.get(), 42);
assert_eq!(cached.access_count(), 2);
// RefCell-based SharedString
let shared = SharedString::new("Hello");
shared.append(", World!");
assert_eq!(shared.get(), "Hello, World!");
// SharedVec
let vec: SharedVec<i32> = SharedVec::new();
vec.push(1);
vec.push(2);
assert_eq!(vec.pop(), Some(2));
assert_eq!(vec.len(), 1);
// SafeCell with try_borrow
let safe = SafeCell::new(42);
let result = safe.with_value(|v| *v * 2);
assert_eq!(result, Some(84));
// Rc<RefCell> pattern with SharedCounter
let counter = SharedCounter::new();
let counter2 = Rc::clone(&counter);
counter.increment();
counter2.increment();
assert_eq!(counter.get(), 2);
// Tree with interior mutability
let root = TreeNode::new("root");
let child1 = TreeNode::new("child1");
root.add_child(child1);
assert_eq!(root.children_count(), 1);Cell<T> uses .get() and .set() - no borrowing, just copying valuesRefCell<T> uses .borrow() for &T and .borrow_mut() for &mut Ttry_borrow() and try_borrow_mut() return Result instead of panickingRc<RefCell<T>>, use Rc::clone() for sharing and borrow()/borrow_mut() for accessRef<T> and RefMut<T> implement Deref and DerefMut, so you can use them like regular referencesSafeCell::is_borrowed(), you can try to borrow and see if it succeedsRefCell will panic on invalid borrows - use try_* methods when unsureTreeNode pattern (Rc<RefCell<Vec<Rc<TreeNode>>>>) is common for tree structures in Rust