Box en Rust: asignación en heap, tipos recursivos y trait objects

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 Trait cuando 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 Deref y Drop: 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 Box entre funciones solo copia el puntero, no los datos del heap.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP