TL;DR:
Box<T>is Rust's simplest smart pointer: it allocates a value of typeTon the heap, owns it, and frees it when theBoxgoes out of scope.Boxhas almost no runtime overhead beyond the heap allocation itself, no reference counting, no locking. The three main use cases are: (1) heap-allocating large values to avoid stack overflow, (2) creating recursive types whose size can't be known at compile time, and (3) using trait objects (Box<dyn Trait>) for runtime polymorphism.
What Is Box<T> in Rust?
Box<T> is an owning pointer to heap-allocated data, the simplest way to put a value on the heap while keeping Rust's ownership guarantees.
fn main() {
// Stack allocation; value lives on the stack
let x = 5;
// Heap allocation; value lives on the heap, Box lives on the stack
let y = Box::new(5);
// Dereference to get the value
println!("{}", *y); // 5
println!("{}", y); // 5; auto-deref in many contexts
// Box is dropped here; heap memory freed automatically
}When a Box<T> goes out of scope, Rust calls the Drop trait implementation, the heap memory is freed without a garbage collector or reference counting.
When Should You Use Box?
The three canonical use cases: heap-allocating for size control, recursive types, and trait objects.
1. Large values or transferring ownership without copying
// Move a large array to the heap to avoid stack overflow
let large = Box::new([0u8; 1_000_000]);
// Box enables moving heap data without copying it
fn process(data: Box<[u8; 1_000_000]>) {
// data pointer is moved (8 bytes); not the million bytes
}2. Recursive types
// COMPILE ERROR; Rust can't know the size of a recursive type
enum List {
Cons(i32, List), // List contains List; infinite size
Nil,
}
// WORKS; Box has a known size (pointer width)
enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
}3. Trait objects (Box<dyn Trait>)
trait Animal {
fn sound(&self) -> &str;
}
struct Dog;
struct Cat;
impl Animal for Dog { fn sound(&self) -> &str { "woof" } }
impl Animal for Cat { fn sound(&self) -> &str { "meow" } }
fn main() {
// Store different concrete types in one Vec using Box<dyn Trait>
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Dog),
];
for animal in &animals {
println!("{}", animal.sound()); // dynamic dispatch via vtable
}
}How Does Box Compare to Other Smart Pointers?
Box is the simplest, single owner, no overhead beyond the allocation. Use Rc/Arc when you need shared ownership.
| Smart pointer | Ownership | Thread-safe | Overhead |
|---|---|---|---|
Box<T> | Single owner | N/A (not shared) | None |
Rc<T> | Shared (single-thread) | ❌ | Reference count |
Arc<T> | Shared (multi-thread) | ✅ | Atomic reference count |
Cell<T> / RefCell<T> | Single owner | ❌ | None / runtime borrow check |
What Is Deref Coercion With Box?
Box<T> implements Deref<Target = T> ; Rust automatically coerces Box<T> to &T in most contexts, making it feel like a regular reference.
fn greet(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let boxed_name = Box::new(String::from("Alice"));
// Box<String> → &String → &str via deref coercion chain
greet(&boxed_name); // works; no manual dereferencing needed
// Explicit dereference
let name: &String = &*boxed_name;
}This deref coercion chain (Box<String> → &String → &str) makes Box transparent in most API contexts.
Frequently Asked Questions
Use generics (<T: Trait>) when all callers use the same type, zero-cost, inlined. Use Box<dyn Trait> when you need to store different types in the same collection, or when the concrete type is unknown until runtime. The trade-off is a heap allocation and a vtable lookup per call.
Box::leak(b) consumes the Box and returns a &'static mut T, the data lives for the rest of the program. Used to create static references from heap data at startup. The memory is never freed (intentionally, it is a deliberate leak).
&T borrows, it does not own the data, which must outlive the reference. Box<T> owns the data, it manages the memory and can be stored in structs without lifetime annotations. Box also allows mutation via &mut *boxed where a plain reference might not have permission.
Only the heap allocation/deallocation, the same as malloc/free. Accessing *box is a single pointer dereference, equivalent to a raw pointer. Box<dyn Trait> adds one vtable indirection per method call.
Sources
Related Glossary Terms
- Ownership:
Boxowns its heap data; dropping theBoxfrees the memory - Trait:
Box<dyn Trait>is the primary way to use trait objects - Arc:
Arc<T>for shared heap ownership across threads - Generic:
Box<T>is generic; prefer generics overBox<dyn Trait>when possible
