Box<T> es el smart pointer más elemental de Rust: reserva espacio en el heap para un valor, mantiene un puntero de tamaño fijo en el stack y libera la memoria automáticamente cuando sale de scope. No tiene overhead en runtime comparado con una referencia ordinaria, pero resuelve tres problemas que sin él serían imposibles o muy incómodos de manejar.
Cuándo usar Box<T>
El compilador necesita conocer el tamaño de todos los tipos en tiempo de compilación. Box<T> aparece cuando eso no es posible o no es conveniente:
- Tipos recursivos como listas enlazadas o árboles, donde el tamaño del tipo depende de sí mismo.
- Mover datos grandes entre scopes sin copiarlos: solo se copia el puntero.
- Trait objects con
dyn Traitcuando necesitas despacho dinámico.
Sintaxis básica
fn main() {
let b = Box::new(5);
println!("b = {b}"); // b = 5
// Box implementa Deref, así que puedes usarlo como referencia
let x = *b + 10;
println!("x = {x}"); // x = 15
} // b se destruye aquí y libera el heap automáticamente
Tipos recursivos: lista enlazada
Sin Box este código no compila porque Lista tendría tamaño infinito:
// ERROR: tipo recursivo tiene tamaño infinito
// enum Lista {
// Cons(i32, Lista),
// Nil,
// }
// CORRECTO: Box rompe el ciclo con un puntero de tamaño fijo
enum Lista {
Cons(i32, Box<Lista>),
Nil,
}
fn main() {
let lista = Lista::Cons(
1,
Box::new(Lista::Cons(
2,
Box::new(Lista::Cons(3, Box::new(Lista::Nil)))
))
);
// Recorrer la lista
let mut actual = &lista;
loop {
match actual {
Lista::Cons(val, siguiente) => {
println!("{val}");
actual = siguiente;
}
Lista::Nil => break,
}
}
}
Árbol binario con Box
#[derive(Debug)]
enum Arbol {
Hoja(i32),
Nodo(Box<Arbol>, Box<Arbol>),
}
impl Arbol {
fn suma(&self) -> i32 {
match self {
Arbol::Hoja(v) => *v,
Arbol::Nodo(izq, der) => izq.suma() + der.suma(),
}
}
}
fn main() {
let arbol = Arbol::Nodo(
Box::new(Arbol::Nodo(
Box::new(Arbol::Hoja(1)),
Box::new(Arbol::Hoja(2)),
)),
Box::new(Arbol::Hoja(3)),
);
println!("Suma: {}", arbol.suma()); // Suma: 6
}
Transferir ownership de datos grandes
struct DatosGrandes {
datos: [f64; 10_000],
}
fn procesar(d: Box<DatosGrandes>) {
println!("Primer elemento: {}", d.datos[0]);
// d se destruye aquí, heap liberado
}
fn main() {
let d = Box::new(DatosGrandes { datos: [1.0; 10_000] });
procesar(d); // solo se mueve el puntero (8 bytes)
}
Trait objects con Box<dyn Trait>
Cuando distintos tipos implementan el mismo trait y no conoces el tipo concreto hasta runtime, Box<dyn Trait> te permite trabajar con todos ellos de forma uniforme:
trait Animal {
fn sonido(&self) -> &str;
}
struct Perro;
struct Gato;
impl Animal for Perro {
fn sonido(&self) -> &str { "guau" }
}
impl Animal for Gato {
fn sonido(&self) -> &str { "miau" }
}
fn crear_animal(tipo: &str) -> Box<dyn Animal> {
match tipo {
"perro" => Box::new(Perro),
"gato" => Box::new(Gato),
_ => panic!("animal desconocido"),
}
}
fn main() {
let animales: Vec<Box<dyn Animal>> = vec![
crear_animal("perro"),
crear_animal("gato"),
crear_animal("perro"),
];
for a in &animales {
println!("{}", a.sonido());
}
}
El error típico sin Box
// Este código no compila:
// fn crear_animal(tipo: &str) -> dyn Animal { ... }
//
// error[E0277]: the size for values of type `dyn Animal` cannot be known
// at compilation time
//
// La solución es Box<dyn Animal> o &dyn Animal
Resumen
Box::new(valor)mueve el valor al heap y devuelve un puntero.- Implementa
DerefyDrop: se usa como referencia y libera automáticamente. - Es la única forma de definir tipos recursivos en Rust.
Box<dyn Trait>habilita polimorfismo dinámico cuando el tipo concreto no se conoce en compilación.- Mover un
Boxentre funciones solo copia el puntero, no los datos del heap.
