Intermediate · Closures, iterators and smart pointers · topic 33 of 50 · 25 min
Box<T>: values on the heap
Explanation
What is a smart pointer?
A pointer holds an address. A smart pointer is a struct that acts like a pointer but also has extra abilities and rules, such as owning the data and cleaning it up. String and Vec are smart pointers too: they own heap memory and free it on drop. Box<T> is the simplest one.
fn main() {
let b = Box::new(5000); // the number 5000 lives on the heap
println!("{}", b + 1); // 5001: Box behaves like the value
} // b is dropped: heap memory freed
STACK HEAP
b: ptr -------> 5000Deref and Drop: what makes it "smart"
Two traits make a Box feel natural. Deref lets you use a Box<T> where a &T is expected and lets * reach the value inside; that is why b + 1 works. Drop runs cleanup code when the value goes out of scope; for Box, it frees the heap memory. You can implement Drop for your own types, for example to log when a connection closes.
struct Connection { id: u32 }
impl Drop for Connection {
fn drop(&mut self) {
println!("closing connection {}", self.id);
}
}
fn main() {
let _a = Connection { id: 1 };
let _b = Connection { id: 2 };
println!("working");
} // prints: working, closing connection 2, closing connection 1Use 1: recursive types
A recursive type contains a value of its own type, like a linked list or a tree. The compiler must know the size of every type. A type that contains itself directly would be infinitely big. A Box has a fixed size (one pointer), so putting the inner value in a Box makes the size known.
// error[E0072]: recursive type `List` has infinite size
// enum List { Cons(i32, List), Nil }
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn sum(list: &List) -> i32 {
match list {
Cons(value, rest) => value + sum(rest),
Nil => 0,
}
}
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{}", sum(&list)); // 6
}list: Cons(1, ptr) --> Cons(2, ptr) --> Cons(3, ptr) --> Nil
stack heap heap heapUse 2: trait objects
You met Box<dyn Trait> in module 7: a pointer to some value of unknown size that implements a trait. This is the most common use of Box in everyday code: lists of handlers, plugins and widgets, or a function that returns one of several types.
Use 3: large values
Moving a big struct (say, a 1 MB array) copies all its bytes. Moving a Box<BigStruct> copies one pointer. This matters rarely; measure before optimising.
When NOT to use Box
- For ordinary small values: just use the value. The stack is faster.
- For lists: use Vec, which already stores its items on the heap.
- For shared ownership: Box has exactly one owner. Use Rc (next topic) or Arc (module 9).
Common mistakes
- Defining a recursive struct or enum without Box: E0072 "recursive type has infinite size". Wrap the recursive field in Box (or Vec, or Option<Box<...>>).
- Boxing everything because other languages put objects on the heap. In Rust, default to plain values.
- Trying to clone a Box<dyn Trait> without the trait supporting it. Cloning trait objects needs extra work.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Box<T>: values on the heap · 1/6
A smart pointer is a struct that acts like a pointer and owns its data. String and Vec are smart pointers. Box is the simplest.
Prefer a video by a teacher? Find free videos on “rust box smart pointer recursive types deref drop” on YouTube ↗
In real life: A category tree for an online store
An e-commerce site has nested categories: Electronics contains Mobiles, which contains Smartphones and Feature phones. Each category holds a list of child categories. Because a Category contains Categories, the children are stored in a Vec (which keeps them on the heap), and a function walks the tree recursively to count every category.
struct Category {
name: String,
children: Vec<Category>, // Vec puts children on the heap, like Box
}
fn count(c: &Category) -> usize {
1 + c.children.iter().map(count).sum::<usize>()
}
fn main() {
let tree = Category {
name: "Electronics".into(),
children: vec![Category {
name: "Mobiles".into(),
children: vec![
Category { name: "Smartphones".into(), children: vec![] },
Category { name: "Feature phones".into(), children: vec![] },
],
}],
};
println!("{} has {} categories", tree.name, count(&tree)); // 4
}Practice (0/3 done)
Why does this not compile? enum Tree { Node(i32, Tree, Tree), Leaf, }
What does this print? struct Noisy(u8); impl Drop for Noisy { fn drop(&mut self) { print!("{} ", self.0); } } fn main() { let _x = Noisy(1); let _y = Noisy(2); }
Build a binary tree of numbers: enum Tree { Node(i32, Box<Tree>, Box<Tree>), Leaf }. Write fn sum(t: &Tree) -> i32 and fn depth(t: &Tree) -> usize, build a small tree by hand, and print both. Add a Drop impl to a wrapper struct to see when it is freed.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Rc, RefCell and WeakClosures, iterators and smart pointers · same module
- ClosuresClosures, iterators and smart pointers · same module
- Pointers, the stack and the heapC++ Programming (modern C++ and DSA) · References, pointers, arrays and strings
- Smart pointers 1: std::unique_ptrC++ Programming (modern C++ and DSA) · Templates, smart pointers and move semantics
- Memory 101: the stack and the heapOwnership: Rust's big idea
- Pointers to structs and the arrow operatorC Programming (from zero to systems) · Structs, unions, enums and files
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Where does Box::new(x) put x?
Answer: On the heap
Box allocates heap memory for the value.
Which trait lets *b reach the value inside a Box?
Answer: Deref
Deref powers * and automatic dereferencing.
How many owners can a Box have?
Answer: One
Box has exactly one owner; for shared ownership use Rc or Arc.
What fixes "recursive type has infinite size"?
Answer: Putting the recursive field behind a pointer like Box
A pointer has a fixed size, so the type size becomes known.
Why must dyn Trait be behind a pointer like Box?Related
Answer: Its size is not known at compile time
Different implementing types have different sizes.
For a struct pointer s, what is s->name the same as?Related
Answer: (*s).name
The arrow operator dereferences the pointer and then picks the member.
When is the object owned by a unique_ptr deleted?Related
Answer: When the unique_ptr is destroyed or reset
The destructor of unique_ptr deletes the object.
Which type is Copy?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Closures, iterators and smart pointers” before its exam: Rc, RefCell and Weak.
- Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Concurrency and async. 50 topics left in the course before the final exam.