007 - Interior Mutability and Tradeoffs
Interior Mutability and Tradeoffs
Why this matters
Rust usually enforces mutability rules at compile time, but some designs need mutation through shared references. Interior mutability enables this—with explicit runtime checks or synchronization.
Learning goals
By the end, you should be able to:
- Explain interior mutability and when to use it.
- Use
Cell<T>andRefCell<T>in single-threaded code. - Understand tradeoffs compared to compile-time borrowing.
Mental model
Cell<T>: copy-in/copy-out mutation forCopytypes.RefCell<T>: runtime borrow checking (borrow/borrow_mut).- If borrowing rules are violated at runtime,
RefCellpanics.
Python baseline
Python commonly mutates shared objects directly. Rust forces an explicit choice to allow that behavior.
Runnable end-to-end example
use std::cell::{Cell, RefCell};
#[derive(Debug)]
struct Stats {
hits: Cell<u32>,
events: RefCell<Vec<String>>,
}
impl Stats {
fn new() -> Self {
Self {
hits: Cell::new(0),
events: RefCell::new(Vec::new()),
}
}
fn record(&self, event: &str) {
// Cell: no mutable reference required
self.hits.set(self.hits.get() + 1);
// RefCell: mutable borrow checked at runtime
self.events.borrow_mut().push(event.to_string());
}
fn summary(&self) {
println!("hits = {}", self.hits.get());
println!("events = {:?}", self.events.borrow());
}
}
fn main() {
let stats = Stats::new();
stats.record("open");
stats.record("parse");
stats.record("save");
stats.summary();
// NOTE: nested borrow_mut() on the same RefCell would panic at runtime.
}
Tradeoffs
Pros:
- Enables practical designs where mutation behind
&selfis useful. - Can simplify APIs in specific scenarios.
Cons:
- Some guarantees move from compile time to runtime.
RefCellmisuse can panic.- Overuse can hide ownership design issues.
Common pitfalls
- Using
RefCellas first choice instead of redesigning ownership. - Holding a borrow longer than necessary.
- Forgetting thread model (
RefCellis not for multi-thread sharing).
Quick practice
- Add
fn clear(&self)that emptiesevents. - Refactor one
RefCelluse to plain&mut selfand compare ergonomics.
Recap
Interior mutability is a useful tool, not a default pattern. Reach for it when it matches the design, and be explicit about runtime tradeoffs.
Next lesson: 008 - Data Layout and Cache-Friendly Rust.
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Memory Mastery Roadmap
- 001 - Memory Mastery: Zero-Cost Abstractions
- 002 - Stack vs Heap, Moves, and Clones
- 003 - Borrowing Rules in Real Functions
- 004 - Lifetimes Without Fear
- 005 - String, &str, and Allocation Patterns
- 006 - Smart Pointers (`Box`, `Rc`, `Arc`, `RefCell`)
- 008 - Data Layout and Cache-Friendly Rust
- 009 - Concurrency Memory Safety (`Send`, `Sync`)
- 010 - Profiling and Benchmarking Rust vs Python