Rust Admin

Guia Completo de Box<T> em Rust: Alocação Explícita no Heap Já leu

O que é Box e Por Que Usar? Box é um tipo de dado inteligente (smart pointer) em Rust que aloca valores no heap em vez de na stack. A stack é uma região de memória rápida mas limitada; o heap é maior mas mais lenta. Quando você tem dados de tamanho desconhecido em tempo de compilação ou precisa passar propriedade entre escopos, Box é a ferramenta certa. Rust é uma linguagem de propriedade. Por padrão, valores vivem na stack e são movidos quando atribuídos a novas variáveis. Box envolve um valor e permite que você o trate como um ponteiro — mantendo a semântica de propriedade intacta. Quando o Box sai do escopo, o valor no heap é automaticamente desalocado. Não há garbage collector; a memória é gerenciada por regras determinísticas. Alocação e Propriedade Quando você cria um Box com , Rust aloca memória no heap e retorna um Box que "possui" esse valor. A propriedade funciona normalmente: quando

O que é Box e Por Que Usar?

Box é um tipo de dado inteligente (smart pointer) em Rust que aloca valores no heap em vez de na stack. A stack é uma região de memória rápida mas limitada; o heap é maior mas mais lenta. Quando você tem dados de tamanho desconhecido em tempo de compilação ou precisa passar propriedade entre escopos, Box é a ferramenta certa.

Rust é uma linguagem de propriedade. Por padrão, valores vivem na stack e são movidos quando atribuídos a novas variáveis. Box envolve um valor e permite que você o trate como um ponteiro — mantendo a semântica de propriedade intacta. Quando o Box sai do escopo, o valor no heap é automaticamente desalocado. Não há garbage collector; a memória é gerenciada por regras determinísticas.

fn main() {
    // Valor na stack
    let x = 5;

    // Valor no heap via Box
    let y = Box::new(5);

    println!("x = {}", x);      // 5
    println!("y = {}", y);      // 5 (Box dereferencia automaticamente em println!)

    // Tamanho em bytes
    println!("Size of x: {}", std::mem::size_of_val(&x));      // 8 (i32)
    println!("Size of y: {}", std::mem::size_of_val(&y));      // 8 (ponteiro)
}

Alocação e Propriedade

Quando você cria um Box com Box::new(valor), Rust aloca memória no heap e retorna um Box que "possui" esse valor. A propriedade funciona normalmente: quando o Box sai do escopo, o valor é desalocado. Você pode transferir propriedade movendo o Box, ou emprestar uma referência usando &box ou &*box.

Este exemplo mostra transferência de propriedade e borrowing:

struct Pessoa {
    nome: String,
    idade: u32,
}

fn imprime_pessoa(p: &Pessoa) {
    println!("{}, {} anos", p.nome, p.idade);
}

fn consome_pessoa(p: Box<Pessoa>) {
    println!("Consumindo: {}", p.nome);
    // Box e seu conteúdo são desalocados aqui
}

fn main() {
    let pessoa = Box::new(Pessoa {
        nome: "Alice".to_string(),
        idade: 30,
    });

    // Emprestando referência - pessoa ainda é dona do Box
    imprime_pessoa(&pessoa);

    // Transferindo propriedade - pessoa perde o Box
    consome_pessoa(pessoa);

    // println!("{}", pessoa.nome); // ERRO: pessoa não existe mais
}

Casos de Uso Práticos

Dados de Tamanho Desconhecido

Traits em Rust não têm tamanho conhecido em tempo de compilação. Se você quer armazenar diferentes tipos implementando a mesma trait, use Box<dyn Trait>:

trait Animal {
    fn fazer_som(&self);
}

struct Cachorro;
struct Gato;

impl Animal for Cachorro {
    fn fazer_som(&self) {
        println!("Au au!");
    }
}

impl Animal for Gato {
    fn fazer_som(&self) {
        println!("Miau!");
    }
}

fn main() {
    let animais: Vec<Box<dyn Animal>> = vec![
        Box::new(Cachorro),
        Box::new(Gato),
        Box::new(Cachorro),
    ];

    for animal in animais {
        animal.fazer_som();
    }
}

Estruturas Recursivas

Tipos recursivos (como árvores ou listas ligadas) precisam de Box para quebrar a dependência circular:

#[derive(Debug)]
enum Nodo {
    Vazio,
    Com(i32, Box<Nodo>),
}

fn main() {
    let lista = Nodo::Com(1, Box::new(
        Nodo::Com(2, Box::new(
            Nodo::Com(3, Box::new(Nodo::Vazio))
        ))
    ));

    println!("{:?}", lista);
    // Com(1, Com(2, Com(3, Vazio)))
}

Otimização de Performance

Valores grandes podem degradar performance se copiados frequentemente. Box evita cópias mantendo um ponteiro:

struct DadosGrandes {
    vetor: Vec<u32>,
}

impl DadosGrandes {
    fn new() -> Self {
        DadosGrandes {
            vetor: vec![0; 1_000_000],
        }
    }
}

fn processa(dados: Box<DadosGrandes>) {
    println!("Processando {} elementos", dados.vetor.len());
}

fn main() {
    let dados = Box::new(DadosGrandes::new());
    processa(dados); // Apenas o ponteiro é movido, não os milhões de elementos
}

Dereferência e Coerção

Box implementa o trait Deref, permitindo acessar o valor através do operador * ou via coerção automática. Rust converte Box<T> em &T automaticamente em contextos onde uma referência é esperada:

fn main() {
    let boxed = Box::new(String::from("Rust"));

    // Dereferência explícita
    println!("Comprimento: {}", (*boxed).len());

    // Dereferência implícita (coerção)
    println!("Maiúsculas: {}", boxed.to_uppercase());

    // Modificação via DerefMut
    let mut numero = Box::new(42);
    *numero += 8;
    println!("Número: {}", numero);
}

Box também implementa DerefMut, permitindo mutabilidade. Se você precisa modificar o valor dentro, declare o Box como mut:

fn incrementa(n: &mut i32) {
    *n += 1;
}

fn main() {
    let mut valor = Box::new(10);
    incrementa(&mut valor);
    println!("{}", valor); // 11
}

Conclusão

Box é fundamental em Rust para alocação explícita no heap. Use-o para: (1) armazenar traits dinamicamente com dyn, eliminando dependências de tamanho em tempo de compilação; (2) criar estruturas recursivas quebrando ciclos de definição; (3) otimizar performance evitando cópias de dados grandes. Rust garante segurança de memória: quando o Box sai do escopo, seu conteúdo é automaticamente liberado.

Referências


Artigos relacionados