RustBox<T>

Box<T> — Heap Allocation

Box<T> is Rust's simplest smart pointer. It allocates a value on the heap and stores a pointer to it on the stack. When the Box goes out of scope, both the pointer and the heap-allocated value are automatically dropped.

Most Rust values live on the stack by default. Box<T> is the standard way to explicitly move a value to the heap when you need to.

Creating a Box

Wrap any value with Box::new() to heap-allocate it. The resulting Box<T> behaves almost identically to the value itself — you can call methods on it directly and dereference it with * to access the inner value.

RUST
fn main() {
    // 5 is stored on the heap; b (the pointer) lives on the stack
    let b = Box::new(5);

    println!("b = {}", b);    // auto-derefs to print 5
    println!("*b = {}", *b);  // explicit dereference also works

    // Box<T> implements Deref, so method calls work transparently
    let s = Box::new(String::from("hello"));
    println!("length: {}", s.len());
} // b and s are dropped here; heap memory is freed
b = 5
*b = 5
length: 5
Note
The variable b itself is a thin pointer on the stack — just the size of a usize. The value 5 lives on the heap. This is why Box<T> has a known, fixed size regardless of what T is.
The Three Main Use Cases
  1. Recursive types — when a type cannot have a known size at compile time because it refers to itself.

  2. Large data transfers — when you want to move ownership of a large value without copying it on the stack.

  3. Trait objects (Box<dyn Trait>) — when you need to store values of different concrete types behind a uniform interface.

Use Case 1 — Recursive Types

Rust requires every type to have a known size at compile time. A recursive type that refers to itself directly breaks this rule — the compiler would need to account for infinite nesting.

The classic example is a cons list. This definition does not compile:

RUST
// This does NOT compile
enum List {
    Cons(i32, List),  // ERROR: recursive type has infinite size
    Nil,
}

The compiler reports: "recursive type List has infinite size".

The fix is to box the recursive reference. Because Box<T> is always one pointer wide, the compiler can determine the exact size of List.

RUST
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    // Build: 1 -> 2 -> 3 -> Nil
    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));

    // Walk the list
    let mut current = &list;
    loop {
        match current {
            Cons(val, next) => {
                print!("{} -> ", val);
                current = next;
            }
            Nil => {
                println!("Nil");
                break;
            }
        }
    }
}
1 -> 2 -> 3 -> Nil
Use Case 2 — Moving Large Data Without Copying

When you return or assign a large struct, Rust normally copies it on the stack. For very large types that can be expensive or even cause a stack overflow. Boxing the value means only the pointer moves — the data stays in place on the heap.

RUST
struct HeavyData {
    buffer: [u8; 1_000_000], // 1 MB — unsafe to copy on the stack
}

fn create_data() -> Box<HeavyData> {
    // Allocated directly on the heap; no large stack copy on return
    Box::new(HeavyData {
        buffer: [0u8; 1_000_000],
    })
}

fn main() {
    let data = create_data();
    println!("first byte: {}", data.buffer[0]);
    // 'data' is dropped here; 1 MB of heap memory is freed
}
Tip
For most everyday structs a stack copy is perfectly fine and faster than a heap allocation. Only reach for Box when the data is genuinely large or when you specifically need indirection.
Use Case 3 — Trait Objects with Box<dyn Trait>

Trait objects let you store values of different concrete types behind the same pointer, enabling runtime polymorphism. Because different implementors of a trait can have different sizes, you need a level of indirection. Boxing each value gives a uniform, pointer-sized handle.

RUST
trait Animal {
    fn speak(&self) -> &str;
}

struct Dog;
struct Cat;
struct Parrot { phrase: String }

impl Animal for Dog    { fn speak(&self) -> &str { "Woof!" } }
impl Animal for Cat    { fn speak(&self) -> &str { "Meow!" } }
impl Animal for Parrot { fn speak(&self) -> &str { &self.phrase } }

fn main() {
    // Vec of trait objects — each element can be a different concrete type
    let animals: Vec<Box<dyn Animal>> = vec![
        Box::new(Dog),
        Box::new(Cat),
        Box::new(Parrot { phrase: String::from("Polly wants a cracker!") }),
    ];

    for animal in &animals {
        println!("{}", animal.speak());
    }
}
Woof!
Meow!
Polly wants a cracker!
Note
The dyn keyword signals dynamic dispatch — the correct method is looked up at runtime via a vtable rather than resolved at compile time. This small indirection cost enables powerful heterogeneous collections.
Box<dyn Error> for Flexible Error Handling

A very common Rust pattern is returning Box<dyn Error> from main or any fallible function where multiple different error types could be returned. The ? operator automatically boxes any error that implements the Error trait.

RUST
use std::error::Error;
use std::fs;

fn parse_first_line(path: &str) -> Result<i32, Box<dyn Error>> {
    let contents = fs::read_to_string(path)?; // std::io::Error
    let first = contents.lines().next().unwrap_or("");
    let number: i32 = first.trim().parse()?;  // ParseIntError
    Ok(number)
}

fn main() -> Result<(), Box<dyn Error>> {
    match parse_first_line("number.txt") {
        Ok(n)  => println!("parsed: {}", n),
        Err(e) => println!("error: {}", e),
    }
    Ok(())
}
Tip
Box<dyn Error> is ideal for applications and scripts. For library code, prefer a custom error enum so callers can match on specific variants without downcasting.
Dereferencing a Box

Box<T> implements the Deref trait, so you can use * to reach the inner value. Rust also applies deref coercions automatically when calling methods or passing references, so in practice you rarely need the explicit *.

RUST
fn print_length(s: &str) {
    println!("length: {}", s.len());
}

fn main() {
    let boxed = Box::new(String::from("hello, world"));

    // Deref coercion: &Box<String> -> &String -> &str automatically
    print_length(&boxed);

    // Direct method call — coercion applies transparently
    println!("uppercase: {}", boxed.to_uppercase());

    // Explicit dereference to get a &String
    let value: &String = &*boxed;
    println!("value: {}", value);
}
length: 12
uppercase: HELLO, WORLD
value: hello, world
Box::leak — Creating &'static References

Box::leak consumes a Box<T> and returns a &'static mut T — a reference that is valid for the entire program lifetime. The memory is intentionally never freed.

This is useful when you need a value to live for the entire program, such as global configuration built at startup.

RUST
fn build_config() -> &'static str {
    let config = Box::new(String::from("production"));
    Box::leak(config)
}

fn main() {
    let cfg: &'static str = build_config();
    println!("config: {}", cfg);
    // 'cfg' is valid for the entire program lifetime
}
Warning
Box::leak intentionally leaks memory — that memory is never reclaimed. Use it only when you genuinely need a &'static reference. For lazy globals, prefer std::sync::OnceLock or theonce_cell crate instead.
Box is Zero-Overhead Beyond the Allocation

After the initial heap allocation there is no runtime overhead to using Box<T>. No reference counting, no locking, no garbage collection. Accessing the inner value is as fast as accessing it through any raw pointer.

Here is how Box compares to other common smart pointers:

Type

Ownership

Thread-safe

Mutable

Overhead

Box<T>

Single owner

Yes (if T: Send)

Yes

One allocation only

Rc<T>

Multiple owners

No

No (use with RefCell)

Reference count

Arc<T>

Multiple owners

Yes

No (use with Mutex)

Atomic reference count

&T / &mut T

Borrowed (no ownership)

Depends on T

Only with &mut

None

When NOT to Use Box
  • Small, stack-sized data — let x = 5; is faster and simpler than Box::new(5).

  • When ownership is clearly single and the data size is known at compile time — the stack is faster.

  • When you need shared ownership — use Rc<T> (single thread) or Arc<T> (multiple threads).

  • When you need shared mutable access — combine Rc<RefCell<T>> or Arc<Mutex<T>>.

Success
Box<T> is the right tool whenever you need heap allocation with single ownership: recursive types, large data transfers, and trait objects. It is simple, deterministic, and adds no hidden runtime costs beyond the allocation itself.