Box<T> es el puntero inteligente más simple de Rust: asigna un valor en el heap y guarda un puntero de tamaño fijo en el stack. Cuando el Box sale de scope, tanto el puntero como el dato en el heap se destruyen automáticamente. Lo usarás principalmente en tres situaciones: tipos recursivos, dispatch dinámico y transfer de ownership de datos grandes sin copiarlos.
Sintaxis básica
fn main() {
let b = Box::new(5); // 5 está en el heap, b en el stack
println!("{}", b); // se desreferencia automáticamente
println!("{}", *b); // desreferencia explícita también funciona
} // b sale de scope ? se libera el heap automáticamente
Por qué Box: los tres casos de uso
1. Tipos recursivos (lista enlazada)
// Sin Box: ERROR de compilación
// enum Lista {
// Cons(i32, Lista), // tamaño infinito, el compilador no puede calcularlo
// Nil,
// }
// Con Box: OK
enum Lista {
Cons(i32, Box<Lista>), // Box tiene tamaño fijo (un puntero)
Nil,
}
fn main() {
let lista = Lista::Cons(1,
Box::new(Lista::Cons(2,
Box::new(Lista::Cons(3,
Box::new(Lista::Nil))))));
}
El compilador necesita conocer el tamaño de cada tipo en compilación. Un tipo recursivo sin Box tendría tamaño infinito. Con Box, el tamaño es siempre el de un puntero (8 bytes en 64 bits).
2. Dispatch dinámico con Box<dyn Trait>
trait Animal {
fn hablar(&self) -> &str;
}
struct Perro;
struct Gato;
impl Animal for Perro {
fn hablar(&self) -> &str { "Guau" }
}
impl Animal for Gato {
fn hablar(&self) -> &str { "Miau" }
}
fn main() {
let animales: Vec<Box<dyn Animal>> = vec![
Box::new(Perro),
Box::new(Gato),
Box::new(Perro),
];
for animal in &animales {
println!("{}", animal.hablar());
}
}
Box<dyn Trait> usa dispatch dinámico (vtable): la llamada se resuelve en runtime en lugar de en compilación. Útil cuando necesitas colecciones de tipos heterogéneos que comparten un trait.
3. Manejo de errores heterogéneos
fn puede_fallar() -> Result<String, Box<dyn std::error::Error>> {
let contenido = std::fs::read_to_string("config.txt")?;
let n: i32 = contenido.trim().parse()?;
Ok(format!("Número: {}", n))
}
Box<dyn Error> acepta cualquier error: io::Error, ParseIntError, etc. No necesitas definir un enum de errores.
Dispatch estático vs dinámico
| Aspecto | impl Trait / T: Trait | Box<dyn Trait> |
|---|---|---|
| Resolución | Compilación (monomorfización) | Runtime (vtable) |
| Rendimiento | Máximo (inlining posible) | Pequeño overhead por vtable |
| Colecciones heterogéneas | No | Sí |
| Tamaño del binario | Mayor (una copia por tipo) | Menor |
Deref coercion
fn imprimir(s: &str) {
println!("{}", s);
}
fn main() {
let s = Box::new(String::from("hola"));
imprimir(&s); // Box<String> ? &String ? &str (deref coercion)
}
Resumen
Box<T>: asigna T en el heap con propiedad única. Se destruye automáticamente.- Tipos recursivos: usa
Boxpara romper el ciclo de tamaño infinito. Box<dyn Trait>: dispatch dinámico para colecciones heterogéneas.Box<dyn Error>: tipo de error comodín en aplicaciones.- Deref coercion:
&Box<T>se convierte automáticamente a&T.
El siguiente artículo cubre Rc<T> y Arc<T>: cómo tener múltiples propietarios del mismo dato mediante reference counting.
