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ção | Descrição | Exemplo |
|---|---|---|
Box::new(valor) | Aloca no heap | Box::new(42) |
*boxed | Desreferencia (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 T | Box::leak(Box::new(val)) |
Box::pin(val) | Cria Pin<Box<T>> | Box::pin(future) |
| Deref coercion | Box<T> → &T automaticamente | fn f(s: &str) {} aceita &Box<String> |
as_ref() | &Box<T> → &T | boxed.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
Custo de alocação: Cada
Box::newaloca memória no heap, o que é mais lento que a stack. Não useBoxpara valores pequenos sem motivo —Box::new(42)é desperdício.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.Mover, não copiar: Quando você faz
let b = Box::new(valor_grande), ovalor_grandeé movido para o heap. A partir daí, mover oBoxé barato (copia apenas o ponteiro).Box::leakvaza memória: Use apenas para dados que precisam viver até o fim do programa. Para dados compartilhados com tempo de vida dinâmico, useRc<T>ouArc<T>.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.Não confunda com
Rc/Arc:Boxé ownership exclusivo — apenas um dono. Se precisar de múltiplos donos, useRc<T>ouArc<T>.
Veja Também
- Smart Pointers em Rust — visão geral de Box, Rc, Arc, Cell, RefCell
- Trait Objects vs Genéricos — quando usar cada abordagem
- Rc<T> e Arc<T> — smart pointers com contagem de referências
- Cow<T>: Clone on Write — alternativa para evitar clonagens
- Option<T> —
Option<Box<T>>para nós opcionais em árvores - Cheatsheet Rust — referência rápida de sintaxe
- Documentação oficial — std::boxed::Box