Rust Smart Pointers
The ownership and borrowing rules cover most situations, but a few real cases don’t fit neatly into “exactly one owner” — a value whose size isn’t known until runtime, data that actually needs more than one owner, or a value you need to mutate through a shared reference. Smart pointers are Rust’s answer to each of those.
Box<T>: putting a value on the heap
Section titled “Box<T>: putting a value on the heap”By default, values live on the stack, and the compiler needs to know their size upfront. Box<T> moves a value to the heap and gives you a fixed-size pointer to it — useful when a type’s size can’t be known at compile time, most commonly with recursive types:
fn main() { let boxed = Box::new(5); println!("{}", *boxed); // * to get the value out}Output:
5A classic example of where you need Box: a type that contains itself, like a linked list node:
enum List { Cons(i32, Box<List>), Nil,}
fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
match list { List::Cons(value, _) => println!("First value: {value}"), List::Nil => println!("Empty"), }}Output:
First value: 1Without Box, Cons(i32, List) would need infinite space to store — a List containing a List containing a List, forever. Box<List> is a fixed-size pointer instead, so the compiler can work out how much space it needs.
Rc<T>: multiple owners
Section titled “Rc<T>: multiple owners”Ownership says a value has exactly one owner. Most of the time that’s exactly right — but sometimes you really do need multiple parts of your program to share the same data. Rc<T> (“reference counted”) allows that, by keeping track of how many owners currently exist:
use std::rc::Rc;
fn main() { let a = Rc::new(String::from("shared data")); let b = Rc::clone(&a); // not a deep copy — just another owner let c = Rc::clone(&a);
println!("{a} {b} {c}"); println!("Owners: {}", Rc::strong_count(&a));}Output:
shared data shared data shared dataOwners: 3Rc::clone doesn’t copy the underlying data — it just increments a counter and hands back another reference to the same value. The data only gets cleaned up once every owner has gone out of scope.
RefCell<T>: mutating through a shared reference
Section titled “RefCell<T>: mutating through a shared reference”Normally, the borrowing rules are checked at compile time: either many read-only references, or one mutable one, never both. RefCell<T> moves that same check to runtime instead, which lets you mutate a value even through what looks like a shared, non-mut reference:
use std::cell::RefCell;
fn main() { let value = RefCell::new(5);
*value.borrow_mut() += 1;
println!("{}", value.borrow());}Output:
6RefCell is most often paired with Rc — Rc<RefCell<T>> gives you a value with multiple owners that can still be mutated, which plain Rc<T> alone doesn’t allow.
When to actually reach for these
Section titled “When to actually reach for these”Don’t start with smart pointers — start with plain ownership and borrowing, and reach for these only when the compiler is actively telling you it won’t fit: a recursive type (Box), a value that actually needs multiple owners (Rc), or mutation through a shared reference that borrowing rules won’t allow (RefCell).
Practice
Section titled “Practice”1. Add up a linked list
Section titled “1. Add up a linked list”Use the List enum from the Box section. Build the list 1, 2, 3 and write a function fn sum(list: &List) -> i32 that adds up every value. Hint: match on the list. For Cons(value, rest), the answer is value plus the sum of rest. For Nil, it’s 0.
Show solution
enum List { Cons(i32, Box<List>), Nil,}
fn sum(list: &List) -> i32 { match list { List::Cons(value, rest) => value + sum(rest), List::Nil => 0, }}
fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil)))))); println!("Sum: {}", sum(&list));}Output:
Sum: 6The function calls itself on the rest of the list until it reaches Nil. Notice you can pass rest (a &Box<List>) where a &List is expected. Rust looks through the Box for you.
2. A shared shopping list
Section titled “2. A shared shopping list”Make one shopping list (a Vec<String>) that two people, alice and bob, both own and can both add to. Each adds one item. Then print the list and the number of owners. Hint: wrap the Vec in Rc<RefCell<...>>, give each person an Rc::clone, and call .borrow_mut().push(...) to add.
Show solution
use std::cell::RefCell;use std::rc::Rc;
fn main() { let list = Rc::new(RefCell::new(Vec::new())); let alice = Rc::clone(&list); let bob = Rc::clone(&list);
alice.borrow_mut().push(String::from("milk")); bob.borrow_mut().push(String::from("bread"));
println!("{:?}", list.borrow()); println!("Owners: {}", Rc::strong_count(&list));}Output:
["milk", "bread"]Owners: 3There are three owners: list, alice and bob. All three point at the same Vec, so an item added through alice is visible through bob and list too. None of the variables needs mut, because RefCell handles the mutation.
Box<T>stores a value on the heap with a fixed-size pointer — needed for recursive types.Rc<T>allows multiple owners of the same value in single-threaded code, via reference counting; useArc<T>across threads.RefCell<T>checks borrowing rules at runtime instead of compile time, allowing mutation through a shared reference — but panics if the rules are broken.- Reach for these only when plain ownership and borrowing don’t fit your situation.
Quick check
Section titled “Quick check”Quick check
1. Why does a recursive type like a linked list node need Box<T>?
2. What does Rc::clone(&a) actually do?
3. When does RefCell<T> check the borrowing rules?
4. What does Rc<RefCell<T>> give you that plain Rc<T> alone doesn't?
Score: 0 / 4
Read more
Section titled “Read more”- Using
Box<T>to Point to Data on the Heap — The Rust Book: the full story ofBox, including the cons list example used on this page. Rc<T>, the Reference Counted Smart Pointer — The Rust Book: shared ownership step by step, watching the count go up and down.RefCell<T>and the Interior Mutability Pattern — The Rust Book: when and why to move borrow checks to runtime, with a realistic example.std::cell— standard library docs: explainsCellandRefCell, and when to pick each one.- Rc — Rust by Example: a short, runnable example that prints the reference count as owners come and go.