Cell and RefCell
Cell and RefCell: Interior Mutability
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 forCopytypes. It moves values in and out rather than borrowing, making it zero-cost but limited to types that implementCopy.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).
When to Use Each
Use Cell<T> when:
- Working with
Copytypes (integers, booleans, small structs) - You need simple get/set operations
- You want zero runtime overhead
Use RefCell<T> when:
- Working with non-
Copytypes (String, Vec, etc.) - You need to borrow the inner value (get a reference)
- You're okay with runtime borrow checking
Key Operations
Cell<T>:
Cell::new(value)- Create a new Cellcell.get()- Get a copy of the value (requiresT: Copy)cell.set(value)- Set a new valuecell.replace(value)- Set value and return old valuecell.take()- Take the value, leavingDefault::default()(requiresT: 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()- ReturnsResult<Ref<T>, BorrowError>refcell.try_borrow_mut()- ReturnsResult<RefMut<T>, BorrowMutError>
Your Task
Implement the following types and functions to demonstrate interior mutability patterns:
Part 1: Cell Basics
-
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 1
-
CachedValue<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 accessed
Part 2: RefCell Basics
-
SharedString- 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 empty
-
SharedVec<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>whereT: Clone- Get element at index
Part 3: Safe Borrowing with try_borrow
SafeCell<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>whereF: FnOnce(&T) -> R- Apply function if not borrowedwith_value_mut<F, R>(&self, f: F) -> Option<R>whereF: FnOnce(&mut T) -> R- Apply mutating function
Part 4: Combining Rc and RefCell
-
SharedCounter- 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 counter
-
TreeNode<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 children
Examples
use 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);Hints
Click here for hints
Cell<T>uses.get()and.set()- no borrowing, just copying valuesRefCell<T>uses.borrow()for&Tand.borrow_mut()for&mut Ttry_borrow()andtry_borrow_mut()returnResultinstead of panicking- When combining
Rc<RefCell<T>>, useRc::clone()for sharing andborrow()/borrow_mut()for access Ref<T>andRefMut<T>implementDerefandDerefMut, so you can use them like regular references- For
SafeCell::is_borrowed(), you can try to borrow and see if it succeeds - Remember that
RefCellwill panic on invalid borrows - usetry_*methods when unsure - The
TreeNodepattern (Rc<RefCell<Vec<Rc<TreeNode>>>>) is common for tree structures in Rust
Run to execute your code and see test results here.