Box<T> es el puntero inteligente más básico y directo de Rust. Su función principal es transferir la propiedad de un valor del stack (pila) al heap (montículo). Mientras que las variables estándar viven en el stack para un acceso rápido, el stack requiere que el compilador conozca el tamaño exacto de cada dato en tiempo de compilación. Box<T> resuelve este problema al almacenar el valor en el heap, dejando en el stack únicamente un puntero de tamaño fijo que apunta a la dirección de memoria donde reside el dato.
Existen tres escenarios críticos donde Box<T> es indispensable. Primero, en tipos recursivos: una estructura que se contiene a sí misma (como un nodo de una lista enlazada) tendría un tamaño infinito si no fuera por el puntero; Box<T> proporciona un tamaño de puntero conocido, rompiendo el ciclo. Segundo, para objetos de trait (Box<dyn Trait>): cuando queremos almacenar diferentes tipos que implementan una misma interfaz, el compilador no sabe cuánto espacio ocupará cada tipo; el Box proporciona la indirección necesaria para manejar esta variabilidad. Tercero, para transferir grandes volmenes de datos: mover un puntero en el stack es mucho más eficiente que copiar grandes estructuras de datos entre funciones.
El uso de Box<T> implica un costo de una única asignación en el heap y una indirección de memoria para acceder al valor. A diferencia de Rc<T>, no tiene el sobrecosto de un contador de referencias, lo que lo hace extremadamente eficiente para la gestión de propiedad única (ownership). Cuando el Box sale de alcance, el valor en el heap es liberado automáticamente (dropped). Gracias a la implementación del trait Deref, podemos acceder al contenido del Box de forma transparente, ya sea mediante el operador de desreferencia * o llamando a métodos directamente.
// Definición de un Trait para demostrar Trait Objects
trait Describir {
fn describir(&self) -> String;
}
struct Perro {
nombre: String,
}
impl Describir for Perro {
fn describir(&self) -> String {
format!("Soy un perro llamado {}", self.nombre)
}
}
// Estructura recursiva para demostrar el uso de Box en tipos de tamaño desconocido
struct Nodo {
valor: i32,
siguiente: Option<Box<Nodo>>, // El Box rompe la recursión infinita
}
fn main() {
// 1. Uso de Box para Recursión (Lista enlazada simple)
let lista = Nodo {
valor: 1,
siguiente: Some(Box::new(Nodo {
valor: 2,
siguiente: None,
})),
};
// 2. Uso de Box como Trait Object (Indirección de tipo)
// Aquí el tamaño de 'animal' es el tamaño del puntero, no del Perro
let animal: Box<dyn Describir> = Box::new(Perro {
nombre: String::from("Fido"),
});
// 3. Acceso mediante desreferencia implícita (Deref)
// println! llama a describir() a través de la indirección del Box
println!("Lista: {} -> {}", lista.valor, lista.siguiente.as_ref().map(|n| n.valor).unwrap_or(0));
println!("Trait Object: {}", animal.describir());
// 4. Acceso explícito al valor en el heap
let valor_en_heap = *animal.as_ref(); // (Conceptualmente similar a desreferenciar)
// Nota: En la práctica, el auto-deref de Rust hace casi todo el trabajo.
}
Análisis del Código
struct Nodo: Define una estructura recursiva. La varianteOption<Box<Nodo>>es crucial; si usáramosOption<Nodo>, el compilador fallaría porque el tamaño deNodosería indeterminado. ElBoxproporciona un tamaño fijo (el tamaño del puntero).animal: Box<dyn Describir>: Aquí estamos creando un trait object. El tipodyn Describires un tipo de tamaño desconocido en tiempo de compilación (DST), por lo que debe vivir detrás de un puntero comoBoxpara que la variableanimalen el stack tenga un tamaño constante.Box::new(Perro { ... }): Esta instrucción reserva memoria en el heap para la instancia dePerroy devuelve unBox<Perro>que reside en el stack.lista.valoryanimal.describir(): Rust utiliza el traitDerefde forma automática (auto-deref). Cuando llamamos adescribir()sobreanimal, Rust sabe que debe seguir la dirección del puntero contenido en elBoxpara encontrar la implementación del método en la memoria del heap.lista.siguiente.as_ref(): Se utilizaas_ref()para obtener una referencia al contenido delOptionsin tomar la propiedad (ownership) delBox, permitiéndonos inspeccionar el valor sin destruir la lista.
N° 53