Box<T> for Heap Allocation
Understand what Box<T> is, why Rust uses heap allocation, and the three main scenarios where Box<T> solves ownership and size problems.
When a value is too large to sit on the stack, or its size can’t be known until runtime, Rust’s default stack storage isn’t enough. Box<T> is the simplest smart pointer that solves this: it moves the value to the heap and keeps a fixed‑size pointer on the stack. In this section you’ll learn when to reach for Box<T>, how it manages memory, and why it is the foundation for more advanced smart pointers like Rc and RefCell.
This page gives you the conceptual map. The two sections that follow — “Using Box<T> to Store Data on the Heap” and “Enabling Recursive Types with Boxes” — dive into code, memory layout details, and the specific patterns that make Box<T> a daily tool.
What Box<T> Is
A Box<T> is an owning pointer to a value that lives on the heap. The variable that holds the Box sits on the stack, but the actual data sits in heap memory. When the Box goes out of scope, Rust automatically drops the heap-allocated value, just like it would for any owned stack value.
let heap_i32 = Box::new(42);
// heap_i32 is a Box<i32> — the i32 lives on the heap.
// Dereferencing it gives the value:
println!("{}", *heap_i32); // prints 42
// You can also use the Box directly: println!("{}", heap_i32);
No Extra Features:
Box<T> is the most minimal smart pointer. It does not provide shared ownership or interior mutability — it only guarantees single ownership and heap allocation. If you need multiple owners, the later sections on Rc<T> and Arc<T> will help.
Mental model — think of a Box as a safe, self‑cleaning version of malloc. You create a boxed value, move the box around without copying the large payload, and you never write free.
Why You Need Heap Allocation
Rust defaults to the stack because it’s fast and predictable. But there are three situations where the stack falls short, and Box<T> steps in.
1. The value’s size isn’t known at compile time
When you try to define a recursive type — like a linked list node that contains another node of the same type — the compiler can’t figure out how much memory to reserve. Box<T> adds a layer of indirection: the pointer inside the recursive type has a fixed size, even though the data it points to may be any size.
// This won't compile — Rust sees infinite size.
enum InvalidList {
Cons(i32, InvalidList),
Nil,
}
With a Box, the compiler sees a fixed‑size pointer and the type becomes finite:
enum List {
Cons(i32, Box<List>),
Nil,
}
The “Enabling Recursive Types with Boxes” section walks through the full error, the reasoning, and the fix.
2. The data is large, and you want to move ownership without copying the whole thing
Stack values are copied bit‑for‑bit when ownership moves (unless they implement Copy, in which case the old variable stays valid). If the type is a large array or struct, that copy is expensive. By putting the large data on the heap, moving a Box only copies the pointer — a few bytes — regardless of the payload’s size.
struct BigData {
content: [u8; 10_000],
}
let heap_big = Box::new(BigData { content: [0; 10_000] });
// Moving heap_big copies only the pointer, not 10,000 bytes.
let moved = heap_big;
// heap_big is no longer valid here — ownership has moved.
Cheap Moves, Safe Ownership:
When you move a Box<T>, only the pointer moves. The large heap data stays put, and Rust still guarantees exactly one owner at any time.
3. You need to own a value and only care about a trait it implements — not its concrete type
Sometimes the exact type is irrelevant; you just want to call methods from a trait. Box<dyn Trait> is a “trait object” that stores the value on the heap and uses dynamic dispatch. This is common for heterogeneous collections or plug‑in systems. Trait objects are covered fully in Chapter 18, but knowing that Box<T> enables them shows how the same indirection concept applies across Rust.
Memory Layout
Understanding the layout helps you see why Box<T> is both small on the stack and safe.
Stack Heap
+-------+ +-------+
| ptr | ------------> | value |
+-------+ +-------+
- The stack frame holds a pointer (8 bytes on a 64‑bit system).
- The heap holds the actual value, however large it may be.
- When the
Boxis dropped, Rust automatically callsdropon the heap value and releases the memory.
A Box<T> always implements the Sized trait, even when T does not. That’s what allows it to appear in places that demand a known size at compile time.
Using Box<T> vs Stack Allocation
For most small, short‑lived values, stack allocation is ideal — it’s faster because memory is simply bumped on the stack pointer. Heap allocation with Box::new requires a request to the allocator and later deallocation, so it’s measurably slower for tiny things like a single i32. A Box<i32> is almost never useful by itself.
The rule of thumb: reach for Box when either the data is large enough that stack copying hurts, or the type system requires indirection (recursion, trait objects). For everything else, let the value sit on the stack.
Don't Box Small Values for Speed:
A Box<i32> does not make your integer faster. In fact, the heap allocation overhead makes it slower. Use Box for size or lifetime needs, not as a performance micro‑optimization.
Common Mistakes and Misconceptions
Several patterns trip up newcomers. Being aware of them early saves compile‑time frustration.
Recursive Type Without Indirection:
Trying to define enum List { Cons(i32, List), Nil } will fail with an “infinite size” error. The fix is to put a Box around the recursive occurrence. This error is so common that the compiler even suggests it.
Moving the Inner Value Leaves the Box Empty:
If you dereference a Box and move the value out — let val = *my_box; — the Box itself remains in scope but its inner value is gone. You cannot dereference it again. Rust’s move semantics apply here just as they do on the stack.
Misconception: “Box is a reference, so I can have multiple Box values pointing to the same data.”
Reality: Box<T> enforces single ownership. If you assign one Box to another, the first one becomes inaccessible. For shared ownership, you need Rc<T> or Arc<T>.
Misconception: “Box<T> automatically dereferences everywhere.”
Reality: Rust’s Deref trait (covered in the next major section) allows Box to be used like a reference in many contexts, but not all. For example, pattern matching on a Box still requires you to dereference explicitly in some positions.