O que é Box e Por Que Usar?
Box
Rust é uma linguagem de propriedade. Por padrão, valores vivem na stack e são movidos quando atribuídos a novas variáveis. Box
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
Boxdyn, 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.