If you’re learning Rust, you’ve probably seen Box<T>
in examples or tutorials. But what exactly is a Box
, and when should you use it?
In this article, we’ll break down the Box
smart pointer in Rust, when to use it, and how it helps with heap allocation, ownership, and recursive data structures.
What is Box<T>
in Rust?
A Box<T>
is a smart pointer in Rust that allows you to store data on the heap instead of the stack.
In Rust, values are usually stored on the stack. But for certain use cases—like large values, recursive data types, or dynamic sizing—you need to store the value on the heap and use a pointer to access it.
That’s exactly what Box<T>
does.
Why Use Box<T>
?
Situation | Why Box<T> Helps |
---|---|
Large data or struct | Avoids stack overflows by allocating on the heap |
Recursive data structures | Enables self-referential types |
Trait objects (dyn Trait ) | Allows dynamic dispatch and heap allocation |
Fixed ownership with minimal overhead | Provides ownership and cleanup without ref counting |
Example: Simple Box<T>
Usage
fn main() {
let b = Box::new(5); // store 5 on the heap
println!("b = {}", b);
}
Here:
5
is allocated on the heap.b
is a pointer to the heap-allocated value.- When
b
goes out of scope, the heap memory is automatically deallocated.
Stack vs Heap in Rust
Feature | Stack | Heap |
---|---|---|
Allocation | Fast (fixed size) | Slower (dynamic) |
Lifetime | Managed by scope | Requires manual/smart pointer |
Size limit | Small | Large |
Use case | Small, short-lived data | Large or unknown-size data |
Use Case: Recursive Data Structures
Rust doesn’t allow infinitely sized types by default. That’s a problem for recursive types like linked lists.
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Nil))));
}
Without Box
, this would cause a compiler error due to unknown size at compile time. Box
gives a known pointer size, making it work.
Ownership and Box<T>
Box<T>
follows Rust’s ownership model:
let a = Box::new(10);
let b = a; // move ownership to b
// println!("{}", a); ❌ Error: `a` is moved
Box vs Rc vs Arc vs Vec
Feature | Box<T> | Rc<T> | Arc<T> | Vec<T> |
---|---|---|---|---|
Heap allocated | ✅ | ✅ | ✅ | ✅ |
Ownership | Single | Shared (single-threaded) | Shared (multi-threaded) | Single |
Thread-safe | ❌ | ❌ | ✅ | ✅ |
Resizable | ❌ | ❌ | ❌ | ✅ |
Use Box
when you:
- Only need single ownership
- Don’t need shared access
- Don’t need to resize or modify the collection
Use Case: Trait Objects and dyn Trait
You can’t store trait objects directly because their size isn’t known. Use Box<dyn Trait>
to solve that.
trait Animal {
fn speak(&self);
}
struct Dog;
impl Animal for Dog {
fn speak(&self) {
println!("Woof!");
}
}
fn main() {
let pet: Box<dyn Animal> = Box::new(Dog);
pet.speak();
}
Here, Box<dyn Animal>
enables dynamic dispatch.
Advanced Use: Passing Boxes Around
Boxes are great for transferring ownership across scopes or threads (if the type is Send
):
fn takes_box(b: Box<i32>) {
println!("Got: {}", b);
}
fn main() {
let b = Box::new(99);
takes_box(b); // b is moved here
}
When to Use Box<T>
in Rust
Use Case | Should Use Box<T> ? | Why |
---|---|---|
You need heap allocation | ✅ | Stores large or dynamic data |
Recursive data structure | ✅ | Makes recursive types possible |
You need dynamic dispatch (dyn Trait ) | ✅ | Stores trait objects with unknown size |
You need shared access | ❌ (Use Rc /Arc ) | Box is single-owner only |
You want to avoid stack overflow | ✅ | Box puts data on the heap |
Box<T>
is one of the most useful and underappreciated features in Rust, especially when you’re building:
- Recursive enums
- Tree or list structures
- Trait-based systems
- Memory-efficient, ownership-safe applications
With Rust’s safety guarantees, Box
gives you heap allocation without a garbage collector, and it does so with zero-cost abstraction.