Box<T>: Alocação no Heap em Rust

Guia completo de Box<T> em Rust: alocação no heap, tipos recursivos, trait objects, Deref coercion, Box::leak e padrões práticos.

O que é Box<T>

Box<T> é o smart pointer mais simples de Rust: ele aloca um valor do tipo T no heap e armazena um ponteiro para ele na stack. Quando o Box sai de escopo, a memória no heap é liberada automaticamente. O Box implementa Deref e DerefMut, então você pode usar o valor contido como se fosse o próprio T na maioria dos contextos.

Use Box<T> em três situações principais: (1) quando tem um tipo cujo tamanho não é conhecido em tempo de compilação (como trait objects dyn Trait), (2) para criar tipos recursivos (árvores, listas ligadas), e (3) quando tem uma estrutura muito grande e quer transferir ownership sem copiar dados na stack. Em todos os outros casos, valores na stack são preferíveis por serem mais eficientes.


Criando Box<T>

fn main() {
    // Alocar um valor simples no heap
    let numero = Box::new(42);
    println!("Valor: {numero}"); // Deref permite usar como i32

    // Operações funcionam normalmente graças ao Deref
    let soma = *numero + 8;
    println!("Soma: {soma}"); // 50

    // Box de uma String (String já está no heap, mas o Box move
    // a estrutura String para o heap também)
    let texto = Box::new(String::from("Olá, Heap!"));
    println!("Texto: {texto}");

    // Box de um array grande (evita stack overflow)
    let dados = Box::new([0u8; 1_000_000]); // 1MB no heap, não na stack
    println!("Tamanho dos dados: {} bytes", dados.len());

    // Box::default()
    let padrao: Box<Vec<i32>> = Box::default(); // Vec vazia no heap
    println!("Padrão: {padrao:?}");
}

Tabela de Métodos e Operações Principais

Método / OperaçãoDescriçãoExemplo
Box::new(valor)Aloca no heapBox::new(42)
*boxedDesreferencia (move o valor para fora)let v = *my_box;
Box::into_raw(b)Converte para ponteiro bruto (unsafe)let p = Box::into_raw(b)
Box::from_raw(p)Cria Box a partir de ponteiro (unsafe)unsafe { Box::from_raw(p) }
Box::leak(b)Vaza memória, retorna &'static mut TBox::leak(Box::new(val))
Box::pin(val)Cria Pin<Box<T>>Box::pin(future)
Deref coercionBox<T>&T automaticamentefn f(s: &str) {} aceita &Box<String>
as_ref()&Box<T>&Tboxed.as_ref()

Exemplos Práticos

1. Tipos Recursivos com Box

Sem Box, o compilador não consegue calcular o tamanho de tipos recursivos. O Box fornece um tamanho fixo (um ponteiro) para quebrar a recursão.

// Lista ligada simples
#[derive(Debug)]
enum Lista<T> {
    No(T, Box<Lista<T>>),
    Vazia,
}

impl<T: std::fmt::Debug> Lista<T> {
    fn nova() -> Self {
        Lista::Vazia
    }

    fn prepend(self, valor: T) -> Self {
        Lista::No(valor, Box::new(self))
    }

    fn iter(&self) -> ListaIter<T> {
        ListaIter { atual: self }
    }
}

struct ListaIter<'a, T> {
    atual: &'a Lista<T>,
}

impl<'a, T> Iterator for ListaIter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        match self.atual {
            Lista::No(valor, proximo) => {
                self.atual = proximo;
                Some(valor)
            }
            Lista::Vazia => None,
        }
    }
}

fn main() {
    let lista = Lista::nova()
        .prepend(3)
        .prepend(2)
        .prepend(1);

    print!("Lista: ");
    for valor in lista.iter() {
        print!("{valor} → ");
    }
    println!("∅");
    // Lista: 1 → 2 → 3 → ∅
}

2. Árvore Binária

#[derive(Debug)]
struct Arvore<T> {
    valor: T,
    esquerda: Option<Box<Arvore<T>>>,
    direita: Option<Box<Arvore<T>>>,
}

impl<T: Ord + std::fmt::Debug> Arvore<T> {
    fn folha(valor: T) -> Self {
        Arvore { valor, esquerda: None, direita: None }
    }

    fn inserir(&mut self, novo: T) {
        if novo < self.valor {
            match &mut self.esquerda {
                Some(esq) => esq.inserir(novo),
                None => self.esquerda = Some(Box::new(Arvore::folha(novo))),
            }
        } else if novo > self.valor {
            match &mut self.direita {
                Some(dir) => dir.inserir(novo),
                None => self.direita = Some(Box::new(Arvore::folha(novo))),
            }
        }
        // Ignora duplicatas
    }

    fn contem(&self, alvo: &T) -> bool {
        if *alvo == self.valor {
            true
        } else if *alvo < self.valor {
            self.esquerda.as_ref().map_or(false, |e| e.contem(alvo))
        } else {
            self.direita.as_ref().map_or(false, |d| d.contem(alvo))
        }
    }

    fn em_ordem(&self) -> Vec<&T> {
        let mut resultado = Vec::new();
        if let Some(esq) = &self.esquerda {
            resultado.extend(esq.em_ordem());
        }
        resultado.push(&self.valor);
        if let Some(dir) = &self.direita {
            resultado.extend(dir.em_ordem());
        }
        resultado
    }
}

fn main() {
    let mut arvore = Arvore::folha(5);
    for n in [3, 7, 1, 4, 6, 8, 2] {
        arvore.inserir(n);
    }

    println!("Em ordem: {:?}", arvore.em_ordem()); // [1, 2, 3, 4, 5, 6, 7, 8]
    println!("Contém 4? {}", arvore.contem(&4));    // true
    println!("Contém 9? {}", arvore.contem(&9));    // false
}

3. Trait Objects com Box<dyn Trait>

trait Forma {
    fn area(&self) -> f64;
    fn nome(&self) -> &str;
}

struct Circulo {
    raio: f64,
}

impl Forma for Circulo {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.raio * self.raio
    }
    fn nome(&self) -> &str {
        "Círculo"
    }
}

struct Retangulo {
    largura: f64,
    altura: f64,
}

impl Forma for Retangulo {
    fn area(&self) -> f64 {
        self.largura * self.altura
    }
    fn nome(&self) -> &str {
        "Retângulo"
    }
}

struct Triangulo {
    base: f64,
    altura: f64,
}

impl Forma for Triangulo {
    fn area(&self) -> f64 {
        self.base * self.altura / 2.0
    }
    fn nome(&self) -> &str {
        "Triângulo"
    }
}

fn main() {
    // Vec de trait objects — tipos diferentes na mesma coleção
    let formas: Vec<Box<dyn Forma>> = vec![
        Box::new(Circulo { raio: 5.0 }),
        Box::new(Retangulo { largura: 10.0, altura: 3.0 }),
        Box::new(Triangulo { base: 8.0, altura: 4.0 }),
    ];

    let area_total: f64 = formas.iter().map(|f| f.area()).sum();

    for forma in &formas {
        println!("{}: área = {:.2}", forma.nome(), forma.area());
    }
    println!("Área total: {area_total:.2}");
}

4. Deref Coercion e Funções Genéricas

fn imprimir_texto(s: &str) {
    println!("Texto: {s}");
}

fn somar_lista(nums: &[i32]) -> i32 {
    nums.iter().sum()
}

fn main() {
    // Deref coercion: Box<String> → String → str
    let boxed_string = Box::new(String::from("Olá via Box!"));
    imprimir_texto(&boxed_string); // Box<String> → &String → &str

    // Deref coercion: Box<Vec<i32>> → Vec<i32> → [i32]
    let boxed_vec = Box::new(vec![1, 2, 3, 4, 5]);
    let soma = somar_lista(&boxed_vec); // Box<Vec<i32>> → &Vec<i32> → &[i32]
    println!("Soma: {soma}"); // 15

    // Funciona com múltiplos níveis
    let duplo_box = Box::new(Box::new(42));
    let valor: i32 = **duplo_box;
    println!("Valor: {valor}");
}

5. Box::leak para Dados com Tempo de Vida Estático

fn criar_config() -> &'static str {
    // Box::leak transforma Box<T> em &'static mut T
    // O valor nunca será liberado (vaza memória intencionalmente)
    let config = format!("versao={}.{}.{}", 1, 0, 0);
    Box::leak(config.into_boxed_str())
}

fn main() {
    let config: &'static str = criar_config();
    println!("Config: {config}");

    // Útil para inicializar globals ou dados que vivem toda a execução
    let dados: &'static [i32] = Box::leak(vec![1, 2, 3, 4, 5].into_boxed_slice());
    println!("Dados estáticos: {dados:?}");
}

Dicas de Performance e Armadilhas

  1. Custo de alocação: Cada Box::new aloca memória no heap, o que é mais lento que a stack. Não use Box para valores pequenos sem motivo — Box::new(42) é desperdício.

  2. Box<dyn Trait> vs genéricos: Trait objects (Box<dyn Trait>) usam despacho dinâmico (vtable), que impede inlining. Se a performance for crítica e você conhece os tipos em compilação, prefira genéricos (T: Trait). Veja Trait Objects vs Genéricos.

  3. Mover, não copiar: Quando você faz let b = Box::new(valor_grande), o valor_grande é movido para o heap. A partir daí, mover o Box é barato (copia apenas o ponteiro).

  4. Box::leak vaza memória: Use apenas para dados que precisam viver até o fim do programa. Para dados compartilhados com tempo de vida dinâmico, use Rc<T> ou Arc<T>.

  5. Tamanho do Box: Box<T> tem o mesmo tamanho de um ponteiro (8 bytes em 64-bit). Box<dyn Trait> ocupa 2 ponteiros (16 bytes): um para os dados e um para a vtable.

  6. Não confunda com Rc/Arc: Box é ownership exclusivo — apenas um dono. Se precisar de múltiplos donos, use Rc<T> ou Arc<T>.


Veja Também