---
title: "21.2 Box<T>"
description: "The simplest smart pointer: heap allocation for a single value, and the recursive types whose infinite-size error Box exists to fix."
url: "https://learnrust.net/chapter-21/box/"
last_updated: "2026-06-13"
---

# 21.2 Box<T>

`Box<T>` is the simplest smart pointer, and the one to learn first. It does exactly one thing: it puts a value of type `T` on the **heap** instead of the stack, and gives you an owning handle to it on the stack. That's it. A `Box<T>` owns its heap value, frees it on drop, and otherwise behaves like the value it holds. Most of the time you won't reach for it, stack values are cheaper and the default, but in a few situations it's exactly the tool, and one of them is unavoidable.

## Boxing a value

Here's a box holding an `i32`:

```rust
fn main() {
    let b = Box::new(5);
    println!("b = {b}");
}
```

```
b = 5
```

`Box::new(5)` allocates space for an `i32` on the heap, stores `5` there, and returns a `Box<i32>`, a stack handle pointing at it. When `b` goes out of scope at the end of `main`, the box is dropped, which frees the heap allocation. Printing `b` prints `5`, because a box transparently stands in for its contents (that's `Deref`, next lesson).

Boxing a lone integer is pointless, an `i32` is perfectly happy on the stack, and this example exists only to show the mechanics. So when *would* you box something? Three cases, and the third is the real reason `Box` exists.

You might box a **large value** to avoid copying it around when it moves: moving a `Box` copies only the small handle, not the big payload. You might box a value to **store it as a trait object**, `Box<dyn Trait>` (lesson [16.9](https://learnrust.net/chapter-16/trait-objects/)), where the concrete type's size isn't known. And you *must* box to build a **recursive type**, which is where we'll spend the rest of the lesson, because it's the case the compiler forces on you.

## The recursive type problem

Suppose you want a classic linked list: each element holds a value and the rest of the list. Following a long tradition, we'll call it a *cons list* (the name comes from Lisp's `cons` function). A natural first attempt with an enum (chapter 11):

```rust
enum List {
    Cons(i32, List),
    Nil,
}
```

`Cons` holds an `i32` and the rest of the list; `Nil` marks the end. It reads perfectly. It also doesn't compile, and the error is the whole point of this lesson:

```
error[E0072]: recursive type `List` has infinite size
 --> src/main.rs:1:1
  |
1 | enum List {
  | ^^^^^^^^^
2 |     Cons(i32, List),
  |               ---- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
  |
2 |     Cons(i32, Box<List>),
  |               ++++    +
```

Read it carefully, because the compiler is teaching the concept. To lay out a `List` on the stack, the compiler needs its size. A `List` is a `Cons`, which contains a `List`, which is a `Cons`, which contains a `List`... The size is "the size of a `List`, plus a bit," an equation with no finite answer. The type has **infinite size**, and the compiler can't allocate it. And look at the help text: it doesn't just report the error, it names the fix and even shows the edit. "Insert some indirection (e.g., a `Box`, `Rc`, or `&`)." This is rustc at its best, and learning to act on messages like this is the skill the whole course has been building.

## Box breaks the cycle

The fix the compiler suggested: put the recursive part behind a `Box`. A `Box<List>` is a *pointer*, and a pointer has a fixed, known size (one machine word) no matter how big the thing it points to is. The size equation now terminates:

```rust
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

    let mut current = &list;
    while let Cons(value, next) = current {
        println!("{value}");
        current = next;
    }
}
```

```
1
2
3
```

Now a `Cons` holds an `i32` and a `Box<List>`. The `Box` is a fixed-size pointer to the next `List` on the heap, so a `List`'s size is "an `i32` plus a pointer", finite and knowable. The list `1 -> 2 -> 3 -> Nil` builds, each `Box::new` heap-allocating the rest. The `while let` (chapter 11) walks it by following each `next` pointer until it hits `Nil`. We've built a recursive data structure, and `Box` is the single ingredient that made it possible.

> **Key insight**
>
> A value's size must be known at compile time to live on the stack (the chapter-8 rule). A recursive type that directly contains itself has no finite size. A pointer, by contrast, is always one word wide regardless of what it points to. So `Box<T>` turns an unsizeable "a `List` contains a `List`" into a sizeable "a `List` contains a *pointer to* a `List`." Every recursive structure in Rust, lists, trees, graphs, is built on this single move: replace the recursive field with a pointer to it.

## Box owns, and frees

A `Box` is a smart pointer, so it owns its contents and cleans up automatically. When `list` is dropped at the end of `main`, its drop frees its boxed `List`, whose drop frees the next box, all the way down the chain, no manual deallocation, no leak (the chapter-8 ownership rules, applied through the boxes). You allocated explicitly with `Box::new`, but you never free explicitly; ownership handles it. That's the smart-pointer promise: heap allocation with the convenience and safety of a stack value.

> **For advanced readers**
>
> A cons list isn't actually how you'd store a sequence in Rust, a `Vec<i32>` (chapter 18) is faster and simpler, because its elements sit contiguously rather than scattered across the heap with a pointer-chase between each. The cons list is a teaching example for *recursion* and `Box`, not a recommendation. Where boxed recursion really earns its place is in genuinely tree-shaped or graph-shaped data: a syntax tree, a file-system hierarchy, an expression evaluator. For a plain list, reach for `Vec`.

## Quiz time

**Question #1**

What does `Box::new(value)` do, and what happens to the heap allocation when the box goes out of scope?

<details class="solution">
<summary>Show solution</summary>

`Box::new(value)` allocates space on the heap, moves `value` into it, and returns a `Box<T>`, a fixed-size owning handle on the stack pointing to the heap data. When the box goes out of scope it's dropped, which frees the heap allocation automatically. You allocate explicitly but never free explicitly; ownership handles deallocation.

</details>

**Question #2**

Why does `enum List { Cons(i32, List), Nil }` fail to compile, and how does `Box` fix it?

<details class="solution">
<summary>Show solution</summary>

It has "infinite size" (E0072): computing a `List`'s size requires knowing a `List`'s size, since `Cons` directly contains a `List`, an equation with no finite answer, so the compiler can't lay it out. Wrapping the recursive field in `Box` (`Cons(i32, Box<List>)`) replaces the inline `List` with a pointer to a heap `List`. A pointer has a fixed, known size, so the type becomes sizeable. (The compiler's error even suggests this fix.)

</details>

**Question #3**

Besides recursive types, name one other reason to use `Box<T>`.

<details class="solution">
<summary>Show solution</summary>

Either: to store a value as a trait object (`Box<dyn Trait>`, lesson 16.9) when the concrete type's size isn't known; or to avoid copying a large value when it moves (moving a `Box` copies only the small pointer, not the big payload). Recursive types are the case the compiler *forces*; these are cases where boxing is a deliberate choice.

</details>

A box stands in transparently for its contents, you printed and walked through one as if it weren't there. That transparency is the `Deref` trait, and the next lesson ([21.3](https://learnrust.net/chapter-21/deref-and-deref-coercion/)) opens it up, finally solving the `&String`-works-as-`&str` mystery from chapter 9.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
