What is interior mutability in Rust and how do Cell and RefCell provide it?
Learn what interior mutability means in Rust and how Cell and RefCell let you mutate data through shared references with runtime-checked borrowing.
Expected Interview Answer
Interior mutability is a Rust pattern that lets you mutate data through a shared (&) reference, moving the borrow rules from compile time to runtime; Cell and RefCell are the standard single-thread types that provide it.
Normally Rust forbids mutation through a shared reference, but sometimes you need it, such as inside an Rc. Cell<T> allows this for Copy-style values by moving values in and out with get and set, never handing out an interior reference, so it cannot violate aliasing. RefCell<T> works for any type by tracking borrows at runtime through borrow and borrow_mut; it enforces the one-writer-or-many-readers rule dynamically and panics if you break it. Both rely on UnsafeCell internally and are single-threaded; the thread-safe equivalents are Mutex and RwLock.
- Allows mutation through shared references when the borrow checker is too strict
- Cell avoids references entirely, making it cheap and panic-free
- RefCell enforces borrow rules at runtime for any type
- Enables mutable state inside Rc for shared, mutable graphs
- Keeps unsafe code contained behind a safe, checked API
AI Mentor Explanation
The borrow checker is like a strict scorer who normally locks the scorebook the moment anyone is reading it. Interior mutability is a special rule letting the scorer edit while others glance at it. Cell swaps the whole page in one motion so nobody sees a half-edit, while RefCell hands out a slip that says either one writer or many readers, and blows the whistle if two writers grab pens at once.
Step-by-Step Explanation
Step 1
Recognize the limitation
The borrow checker forbids mutating data through a shared & reference at compile time.
Step 2
Reach for interior mutability
Use a Cell or RefCell to move the borrow check to runtime when a shared reference must allow mutation.
Step 3
Pick Cell for simple values
Use Cell<T> for Copy values; call get and set to swap values without exposing references.
Step 4
Pick RefCell for any type
Use RefCell<T> and call borrow or borrow_mut to get checked references, respecting the one-writer rule.
Step 5
Combine with Rc when sharing
Wrap as Rc<RefCell<T>> to get shared, mutable state on a single thread; use Arc<Mutex<T>> across threads.
What Interviewer Expects
- Defines interior mutability as mutation through a shared reference
- Knows the borrow check moves from compile time to runtime
- Explains Cell uses get/set and hands out no references
- Explains RefCell tracks borrows and panics on violation
- Knows Rc<RefCell<T>> and the thread-safe Arc<Mutex<T>> equivalents
Common Mistakes
- Thinking RefCell makes borrow rules disappear rather than move to runtime
- Causing a panic by calling borrow_mut while another borrow is active
- Using RefCell across threads instead of Mutex
- Reaching for Cell with a non-Copy type where it will not fit
- Overusing interior mutability instead of restructuring ownership
Best Answer (HR Friendly)
“Interior mutability is a way to change data even when Rust would normally treat it as read-only because it is shared. Cell does this by swapping whole values in and out, and RefCell does it by checking the borrowing rules while the program runs instead of when it compiles, stopping the program if the rules are broken.”
Code Example
use std::cell::{Cell, RefCell};
use std::rc::Rc;
struct Counter {
hits: Cell<u32>, // simple Copy value
log: RefCell<Vec<String>>, // any type, borrow-checked at runtime
}
fn main() {
let c = Counter {
hits: Cell::new(0),
log: RefCell::new(Vec::new()),
};
// Mutate through a shared reference &c
c.hits.set(c.hits.get() + 1);
c.log.borrow_mut().push("first hit".to_string());
println!("hits = {}", c.hits.get());
println!("log = {:?}", c.log.borrow());
// Shared, mutable state via Rc<RefCell<T>>
let shared = Rc::new(RefCell::new(0));
let clone = Rc::clone(&shared);
*shared.borrow_mut() += 10;
println!("shared = {}", clone.borrow()); // 10
}Follow-up Questions
- Why does RefCell panic instead of failing at compile time?
- When would you choose Cell over RefCell?
- How is Rc<RefCell<T>> used to build shared mutable graphs?
- What are the thread-safe equivalents of Cell and RefCell?
- What role does UnsafeCell play under the hood?
MCQ Practice
1. What does interior mutability allow?
Interior mutability lets you mutate data through a shared & reference by enforcing borrow rules at runtime instead of compile time.
2. What happens if you call borrow_mut on a RefCell while another borrow is active?
RefCell checks borrows dynamically and panics if you violate the one-writer-or-many-readers rule at runtime.
3. Which type is best for a simple Copy value needing interior mutability?
Cell works for Copy values via get and set, hands out no references, and never panics, making it the lightweight choice.
Flash Cards
What is interior mutability? — Mutating data through a shared & reference by moving borrow enforcement from compile time to runtime.
How does Cell provide it? — By moving values in and out with get/set for Copy types, never exposing an interior reference.
How does RefCell provide it? — By tracking borrows at runtime via borrow/borrow_mut and panicking if the aliasing rules are broken.
Thread-safe equivalents? — Mutex and RwLock replace RefCell across threads; atomics replace Cell for simple values.