Use Box<T> when you need to allocate a value on the heap to ensure a fixed size at compile time, enable recursive data structures, or satisfy trait object requirements. It acts as a smart pointer that owns the data, automatically deallocating the heap memory when the Box goes out of scope.
Here is a practical example showing how to allocate a large struct on the heap to avoid stack overflow risks, and how to use Box for recursive types which are impossible with stack allocation:
// 1. Allocating a large value on the heap
fn main() {
// This allocates the vector on the heap, keeping the stack footprint small
let large_data: Box<Vec<i32>> = Box::new(vec![1, 2, 3, 4, 5]);
println!("First element: {}", large_data[0]);
// 2. Recursive data structure (impossible without Box)
// Without Box, the compiler can't determine the size of the type
enum List {
Cons(i32, Box<List>),
Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
// Deref coercion allows accessing the inner value directly
println!("List head: {}", match list {
List::Cons(val, _) => val,
List::Nil => 0,
});
// Memory is automatically freed here when `large_data` and `list` drop
}
You can also use the Box::new() constructor with any type, or the box keyword in older Rust versions (though Box::new is preferred). When you need to move ownership of the heap allocation without copying the data, simply assign the Box to another variable. If you need to access the inner value without moving the Box, use dereferencing (*box_var) or the Deref trait implementation which Box provides.
For trait objects, Box is essential because the compiler needs a concrete size for the pointer itself, even if the underlying trait object has a dynamic size:
trait Animal {
fn speak(&self);
}
struct Dog;
impl Animal for Dog {
fn speak(&self) { println!("Woof!"); }
}
fn main() {
// Store a trait object on the heap
let animal: Box<dyn Animal> = Box::new(Dog);
animal.speak(); // Works via dynamic dispatch
}
Remember that Box<T> adds a small overhead of one pointer size (8 bytes on 64-bit systems) compared to stack allocation, so only use it when necessary for recursion, large data, or dynamic dispatch.