19.4 Box<T>
: The Simplest Smart Pointer
Box<T>
is often a programmer’s first encounter with Rust smart pointers. Calling Box::new(value)
allocates value
on the heap and returns a Box<T>
stored on the stack. This “boxes” the data, moving it off the stack. As a result, Box<T>
allows you to store values on the heap while retaining ownership and automatic cleanup.
19.4.1 Key Features of Box<T>
-
Pointer Layout:
ABox<T>
is essentially just a pointer to heap data, with no extra reference counts or complex metadata. -
Validity Guarantees:
Unlike C pointers, aBox<T>
cannot be null or invalid in safe Rust. Creating aBox<T>
from an invalid pointer requiresunsafe
code. -
Ownership and Automatic Cleanup:
TheBox<T>
owns its data. When it goes out of scope, Rust automatically frees the heap memory. No manualfree()
calls are needed. -
Deref
Integration:
TheDeref
trait lets you treat aBox<T>
much like a reference, simplifying access to the underlying value.
19.4.2 Use Cases and Trade-Offs of Box<T>
Use Cases:
-
Recursive Data Structures:
Recursive types like linked lists or trees often need heap allocation for flexible structure.Box<T>
overcomes compile-time size restrictions by storing nodes on the heap. -
Dynamic Dispatch with Trait Objects:
Storingdyn Trait
objects usually requires a pointer type likeBox<dyn Trait>
, enabling dynamic dispatch without knowing the concrete type at compile time. -
Reducing Stack Usage:
Large data can be moved to the heap usingBox<T>
, conserving stack space—handy in deeply recursive functions or systems with limited stack memory. -
Efficient Moves of Large Data:
Moving aBox<T>
only copies the pointer, not the data, avoiding expensive deep copies for large structures. -
Optimizing Memory in Enums:
Storing large data inline in an enum variant can inflate the size of the entire enum, making every instance large. By placing large fields in aBox<T>
, the enum itself holds only a pointer to heap-allocated data. This keeps the enum’s in-memory footprint smaller since it stores just a pointer internally, while the large data resides on the heap.
Trade-Offs:
-
Indirection Overhead:
Accessing heap-allocated data requires an extra pointer dereference, which can be slower than direct stack access. -
Allocation and Deallocation Costs:
Allocating and freeing memory on the heap is typically slower than using the stack. -
Cache Performance:
Heap-allocated data may have poorer locality, possibly increasing cache misses.
Example:
fn main() { let val = 5; let b = Box::new(val); println!("b = {}", b); // Deref makes `b` usable like a reference } // `b` is dropped, and the heap memory is freed automatically