Box<T>
Goal of This Episode
Learn to put data on the heap with Box<T>, and understand why it’s necessary for recursive types.
Concept
Remember Chapter 4’s safe analogy? A key hangs on the keychain, the key opens a safe, and the safe holds the real goods.
Box<T> is the key to that safe — the data lives on the heap, and the Box value on the stack lets Rust reach it.
Why Do We Need Box?
Most of the time, Rust putting data straight on the stack is fine. But two situations call for Box:
1. The data is too big
If a struct has many fields and takes lots of space, the stack may not be a great place for it (stack space is limited). Box moves it to the heap, leaving only the “key” on the stack. This kind of information used to find data stored elsewhere is called a pointer.
2. Recursive types
The more important reason. Suppose you want to define a linked list:
enum List {
Node(i32, List), // Compile error!
Empty,
}
fn main() {}
Rust needs to know every type’s size at compile time. But here’s the problem: to know List’s size, you need to know how big Node is. Node holds an i32 and a List — so you need List’s size. But List contains another List…
Expanding it: List’s size = i32 + List’s size = i32 + i32 + List’s size = … it never terminates. The compiler flat-out errors: “recursive type has infinite size.”
The fix is Box:
enum List {
Node(i32, Box<List>),
Empty,
}
fn main() {}
Box<List> has a fixed size (a pointer’s size), and the problem is solved.
Using a Box
fn main() {
let x = Box::new(42);
println!("{}", x); // Usable directly; Rust fetches the inner value automatically
}
Box::new(value) moves the value onto the heap. The Box owns its contents and releases them automatically at scope exit (since Box implements Drop).
Example Code
// A recursive type via Box: a linked list
enum List {
Node(i32, Box<List>),
Empty,
}
// Printing the list
fn print_list(list: &List) {
match list {
List::Node(value, next) => {
print!("{} -> ", value);
print_list(next);
}
List::Empty => {
println!("end");
}
}
}
fn main() {
// Basic Box usage
let x = Box::new(42);
println!("The value in the Box: {}", x);
// Building a linked list step by step: 3 -> 2 -> 1 -> end
// Starting from the tail
let list = List::Empty; // end
let list = List::Node(1, Box::new(list)); // 1 -> end
let list = List::Node(2, Box::new(list)); // 2 -> 1 -> end
let list = List::Node(3, Box::new(list)); // 3 -> 2 -> 1 -> end
print_list(&list);
// A Box is a single key — the key isn't Copy, so let b = a is a move
let a = Box::new(String::from("hello"));
let b = a; // The key passes from a to b, leaving a empty
// println!("{}", a); // Compile error! a has been moved
println!("{}", b);
}
Recap
Box<T>puts data on the heap, leaving only a pointer on the stack (the “key” from the safe analogy).- Its most important use: recursive types (like linked lists) need
Boxto break the infinite-size problem. Box::new(value)creates theBox; it’s released automatically at scope exit.- A
Boxis a single key; moving it follows the same rules as moving other non-Copyvalues.