Skip to content

Intermediate · Closures, iterators and smart pointers · topic 33 of 50 · 25 min

Box<T>: values on the heap

Explanation

Box<T> is the simplest smart pointer: it puts a value on the heap and owns it, and the heap memory is freed when the Box is dropped. Use it for recursive types (a type that contains itself), for trait objects (Box<dyn Trait>), and occasionally to move a large value around cheaply.

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.

output
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 ------->  5000
Putting a single i32 on the heap is pointless in practice; it is just the simplest example.

Deref 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.

rust
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 1
Values are dropped in reverse order of creation.

Use 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.

rust
// 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
}
Each Cons holds a number and a pointer to the rest of the list.
output
list:  Cons(1, ptr) --> Cons(2, ptr) --> Cons(3, ptr) --> Nil
         stack            heap             heap             heap
Every node after the first lives on the heap, reached through a Box.

Use 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

Box<T> = owned value on the heap

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.

rust
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
}
A Vec<Category> works like Box for recursion: the size of Category stays fixed.

Practice (0/3 done)

Task 1 · Question

Why does this not compile? enum Tree { Node(i32, Tree, Tree), Leaf, }

Task 2 · Question

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); }

Task 3 · Practice

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

Question 1

1.Where does Box::new(x) put x?

Question 2

2.Which trait lets *b reach the value inside a Box?

Question 3

3.How many owners can a Box have?

Question 4

4.What fixes "recursive type has infinite size"?

Read more (free)

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.

From Trait objects and dynamic dispatch

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.

From Pointers, the stack and the heap

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.

From Smart pointers 1: std::unique_ptr

Which type is Copy?Related

Answer: char

char is a simple stack-only type, so it is Copy.

From Copy and Clone

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. 1 more topic in “Closures, iterators and smart pointers” before its exam: Rc, RefCell and Weak.
  2. Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Concurrency and async. 50 topics left in the course before the final exam.

← Previous: Iterators