006 - Smart Pointers (`Box`, `Rc`, `Arc`, `RefCell`)
Smart Pointers (Box, Rc, Arc, RefCell)
Why this matters
Rust’s default ownership model is simple, but real applications need shared ownership and controlled mutability. Smart pointers are the tools for that.
Learning goals
By the end, you should be able to:
- Choose between
Box,Rc, andArcbased on ownership needs. - Explain when
RefCellis useful and what tradeoff it introduces. - Avoid common smart-pointer anti-patterns.
Mental model
Box<T>: single owner, heap allocation.Rc<T>: shared ownership in single-threaded code.Arc<T>: shared ownership across threads.RefCell<T>: interior mutability with runtime borrow checks (single-threaded).
Python baseline
Python objects are reference-counted and shared by default. Rust makes sharing explicit so you can reason about aliasing and mutation safely.
Runnable end-to-end example
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 1) Box<T>: single ownership on heap
let boxed = Box::new(String::from("boxed value"));
println!("box = {}", boxed);
// 2) Rc<T> + RefCell<T>: shared ownership + interior mutability (single-thread)
let shared_counter = Rc::new(RefCell::new(0));
let a = Rc::clone(&shared_counter);
let b = Rc::clone(&shared_counter);
*a.borrow_mut() += 1;
*b.borrow_mut() += 1;
println!("rc/refcell counter = {}", shared_counter.borrow());
// 3) Arc<T> + Mutex<T>: shared ownership + synchronized mutability (multi-thread)
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..4 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut n = counter.lock().unwrap();
*n += 1;
}));
}
for h in handles {
h.join().unwrap();
}
println!("arc/mutex counter = {}", *counter.lock().unwrap());
}
Common pitfalls
- Using
Rc<T>in multi-threaded code (useArc<T>instead). - Reaching for
RefCell<T>too early instead of restructuring ownership. - Nesting smart pointers without a clear reason.
Quick decision guide
- Need heap allocation with one owner?
Box<T> - Need many owners in one thread?
Rc<T> - Need many owners across threads?
Arc<T> - Need mutation through shared references (single-thread)?
RefCell<T>
Quick practice
- Replace
Rc<RefCell<i32>>with plain ownership in a simpler version of the code. - Add another thread to the
Arc<Mutex<_>>example and verify final count.
Recap
Smart pointers are explicit tradeoffs: ownership model, mutability model, and thread model. Pick the simplest pointer that matches your use case.
Next lesson: 007 - Interior Mutability and Tradeoffs.
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
- 007 - Interior Mutability and Tradeoffs
- 008 - Data Layout and Cache-Friendly Rust
- 009 - Concurrency Memory Safety (`Send`, `Sync`)
- 010 - Profiling and Benchmarking Rust vs Python