In this lesson, you will learn how to distinguish between the performance benefits of stack-allocated values and the strategic necessity of Box<T> for heap allocation.
The Stack vs. The Heap
By default, Rust allocates variables on the stack. The stack is extremely fast because it operates with a fixed size known at compile-time. However, the stack has strict limitations: it cannot grow, and it cannot easily house types whose size is unknown until the program runs (like recursive data structures).
When we use Box<T>, we are explicitly moving a value from the stack to the heap. The stack variable now only holds a pointer to that heap-allocated data.
When to Borrow, When to Box
A common beginner mistake is using Box out of habit. In Rust, borrowing is almost always superior to boxing.
- Use Borrowing (
&T) when you simply need to access data that already lives elsewhere. Borrowing has zero allocation cost and zero overhead. - Use
Box<T>only when:- Recursive Types: You are defining a type like a linked list node where the size must be calculated indirectly to avoid infinite recursion.
- Large Data Movement: You want to transfer ownership of a massive struct without copying the entire memory block. Moving a
Boxonly moves the pointer (a few bytes), not the data it points to. - Trait Objects: You need to store different types that implement the same trait in a single collection (
Vec<Box<dyn Trait>>), as the compiler needs a fixed-size pointer to accommodate varying underlying sizes.
Example
Consider a large struct that you need to pass through several functions:
struct HugeData([u8; 10000]);
// BAD: Moving this on the stack copies 10,000 bytes every time
fn process(data: HugeData) {}
// GOOD: Moving a Box only copies a pointer, leaving the data on the heap
fn process_efficiently(data: Box<HugeData>) {}
If you don’t need to transfer ownership or store a recursive structure, keep it on the stack or pass by reference. Using Box incurs a heap allocation cost and adds a layer of indirection, which can hurt cache locality if used excessively.
Key Takeaway: Use Box only when you must control lifetime indirection, allow recursive sizing, or move massive data without copying; otherwise, prefer stack allocation or borrowing.