Box<T>
Box
is an owned pointer to data on the heap:
fn main() { let five = Box::new(5); println!("five: {}", *five); }
Box<T>
implements Deref<Target = T>
, which means that you can call methods
from T
directly on a Box<T>
.
Recursive data types or data types with dynamic sizes need to use a Box
:
#[derive(Debug)] enum List<T> { Cons(T, Box<List<T>>), Nil, } fn main() { let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); println!("{list:?}"); }
-
Box
is likestd::unique_ptr
in C++, except that it’s guaranteed to be not null. -
In the above example, you can even leave out the
*
in theprintln!
statement thanks toDeref
. -
A
Box
can be useful when you:- have a type whose size that can’t be known at compile time, but the Rust compiler wants to know an exact size.
- want to transfer ownership of a large amount of data. To avoid copying large amounts of data on the stack, instead store the data on the heap in a
Box
so only the pointer is moved.
-
If
Box
was not used and we attempted to embed aList
directly into theList
, the compiler would not compute a fixed size of the struct in memory (List
would be of infinite size). -
Box
solves this problem as it has the same size as a regular pointer and just points at the next element of theList
in the heap. -
Remove the
Box
in the List definition and show the compiler error. “Recursive with indirection” is a hint you might want to use a Box or reference of some kind, instead of storing a value directly.
More to Explore
Niche Optimization
#[derive(Debug)] enum List<T> { Cons(T, Box<List<T>>), Nil, } fn main() { let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); println!("{list:?}"); }
A Box
cannot be empty, so the pointer is always valid and non-null
. This
allows the compiler to optimize the memory layout: