Recursive types (via Box<T>)Concept
Explanation
A type that contains itself directly — a linked-list node holding another
node, a tree node holding child nodes of the same type — can't be
represented as-is, because the compiler needs to know a type's exact size
at compile time, and a type containing itself would have to be infinitely
large to compute that: an enum like Cons(i32, List) would need List
to already know its own size before it could compute it.
Wrapping the recursive occurrence in Box<T>
breaks the infinite-size problem: for any sized pointee, a Box is one
pointer-sized value regardless of what it points to (a trait object or
slice makes it a two-word fat pointer, but Box<List> here points to a
sized List), so List above has a fixed, computable size (an enum
discriminant plus the larger of its variants, one of which is just a
pointer) even though it logically contains an unbounded chain of itself. This is the standard, idiomatic
way to write linked lists, trees, and other self-referential data
structures in Rust — the indirection through the heap is exactly what
makes the recursion possible to represent at all.
Basic usage example
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Nil)))); // <- Box gives each Cons a fixed, known size
Restriction: each level of nesting is a real heap allocation, and dropping a very long chain recurses one stack frame per element — an extremely deep list can overflow the stack on drop, which is why production code often reworks deeply recursive structures into an iterative form instead.
Best practices & deeper information
Branching on data (pattern matching)
Matching directly on a Box-recursive enum reads no differently than
matching any other enum — each arm binds rest: &Box<List>, and the
recursive sum(rest) call deref-coerces the &Box<List> to &List at
each step.
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn sum(list: &List) -> i32 {
match list { // <- recurses through the Box at each step, one match arm per shape
Cons(value, rest) => value + sum(rest), // <- rest: &Box<List>, matched and recursed into directly
Nil => 0,
}
}
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{}", sum(&list)); // 6
Why this way: the
Rust Book walks
through this exact Cons-list shape — matching straight through the
Box keeps the recursive walk as simple to read as any non-boxed enum,
despite the heap indirection each Cons involves underneath.