Capítulo 15: Smart Pointers: Box, Rc, Arc, RefCell e Mutex
Capítulo 15: Smart Pointers: Box, Rc, Arc, RefCell e Mutex
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual da Família de Smart Pointers e Gestão de Memória
flowchart TD
SmartPointers["Smart Pointers em Rust (Ponteiros com Metadados e Traits Deref/Drop)"] --> AlocacaoHeap["Alocação Direta na Heap"]
SmartPointers --> PosseCompartilhada["Contagem de Referências (Múltiplos Donos)"]
SmartPointers --> MutabilidadeInterior["Mutabilidade Interior (Bypass em Tempo de Compilação)"]
subgraph HEAP_BOX["Alocação de Heap Exclusiva"]
BoxPtr["Box<T>: Tamanho fixo na Stack, dado na Heap (Tipos Recursivos & dyn Trait)"]
end
subgraph REF_COUNTING["Reference Counting"]
RcPtr["Rc<T>: Single-Thread (Rápido, não atômico)"]
ArcPtr["Arc<T>: Multithread Atômico (Atomic Reference Counting)"]
end
subgraph INTERIOR_MUTABILITY["Mutabilidade Interior"]
RefCellPtr["RefCell<T>: Checagem de Empréstimo em Runtime (Single-Thread)"]
MutexPtr["Mutex<T>: Bloqueio Exclusivo Thread-Safe (Multithread)"]
end
AlocacaoHeap --- HEAP_BOX
PosseCompartilhada --- REF_COUNTING
MutabilidadeInterior --- INTERIOR_MUTABILITY
ArcPtr & MutexPtr --> ArcMutex["Arc<Mutex<T>>: Padrão Ouro para Compartilhamento e Mutação Multithread"]
style SmartPointers fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style HEAP_BOX fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style REF_COUNTING fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style INTERIOR_MUTABILITY fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style ArcMutex fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
🏛️ 1. O que é um Smart Pointer em Rust?
Enquanto referências normais (&T) são apenas ponteiros simples que emprestam dados, os Smart Pointers (Ponteiros Inteligentes) são estruturas de dados que atuam como ponteiros, mas possuem capacidades e metadados extras.
Na biblioteca padrão do Rust, smart pointers são implementados utilizando structs que implementam dois traits canônicos:
Deref(std::ops::Deref): Permite tratar a struct do smart pointer como uma referência direta através do operador de desreferenciação*ponteiroe habilita a mágica do Deref Coercion.Drop(std::ops::Drop): Permite personalizar o código executado exatamente no momento em que a última instância sai de escopo (liberação automática de memória, desalocação de buffers, liberação de locks de rede ou arquivos).
📦 2. Box<T>: Alocação Exclusiva na Heap
O Box<T> é o smart pointer mais elementar do Rust. Ele aloca o dado diretamente na Heap e mantém apenas um ponteiro de 8 bytes na Stack:
Casos de Uso Críticos do Box<T>:
- Tipos Recursivos (Tamanho Indefinido na Compilação): O Rust precisa saber o tamanho exato de cada struct na stack. Em tipos recursivos (como listas encadeadas ou árvores binárias), usar
Box<T>quebra o tamanho infinito fornecendo um ponteiro de tamanho fixo:enum ArvoreBinaria { No(i32, Box<ArvoreBinaria>, Box<ArvoreBinaria>), Folha, } - Transferir Grandes Volumes de Dados sem Cópia na Pilha: Mover um
Boxcopia apenas o ponteiro de 8 bytes na stack, deixando o buffer pesado intacto na Heap. - Trait Objects:
Box<dyn Trait>para coleções heterogêneas.
👥 3. Rc<T> e Arc<T>: Contagem de Referências
Em alguns grafos e topologias de rede, um mesmo nó pode ter múltiplos pais ou proprietários. Como o sistema de ownership do Rust impõe a regra de apenas um dono por vez, recorre-se ao Reference Counting:
3.1 Rc<T> (Reference Counted - Single-Thread)
- Mantém internamente um contador de quantas variáveis apontam para aquele buffer.
- Cada invocação de
Rc::clone(&ptr)incrementa o contador. - Quando uma variável sai de escopo, o contador é decrementado. Quando atinge zero, a memória na Heap é liberada.
- AVISO:
Rc<T>utiliza inteiros comuns para o contador. NÃO É THREAD-SAFE e não implementa o traitSend. O compilador rejeitará seu envio para outras threads!
3.2 Arc<T> (Atomic Reference Counted - Multithread)
- Idêntico ao
Rc<T>, porém o contador de referências utiliza instruções de hardware atômicas da CPU (Atomic Operations). - É 100% thread-safe (implementa
SendeSync), podendo ser compartilhado entre threads do sistema operacional com custo mínimo de sincronização.
🔓 4. O Padrão de Mutabilidade Interior: RefCell<T> e Mutex<T>
O modelo de empréstimo padrão do Rust valida as regras de imutabilidade estritamente em tempo de compilação. Porém, há casos em que você precisa alterar dados internos de uma struct que externamente aparenta ser imutável (Interior Mutability Pattern).
4.1 RefCell<T> (Single-Thread)
Transfere as regras do Borrow Checker para o tempo de execução:
- Em vez de
&e&mut, utiliza métodos.borrow()e.borrow_mut(). - Se você tentar obter dois
.borrow_mut()simultâneos em runtime, oRefCelldispara um panic para impedir corrupção.
4.2 Mutex<T> (Multithread Mutual Exclusion)
O equivalente thread-safe da mutabilidade interior:
- Garante que apenas uma thread por vez possa acessar o dado protegido.
- O método
.lock()bloqueia a thread atual até que a trava seja adquirida, retornando umMutexGuardinteligente que libera a trava automaticamente ao sair de escopo viaDrop.
💎 5. A Composição Padrão Ouro: Arc<Mutex<T>>
Para compartilhar e modificar estado global entre múltiplas threads de alta performance, a indústria utiliza a consagrada combinação Arc<Mutex<T>>:
use std::sync::{Arc, Mutex};
use std::thread;
let estado_global = Arc::new(Mutex::new(0));
let mut threads = vec![];
for _ in 0..5 {
let estado_clone = Arc::clone(&estado_global);
threads.push(thread::spawn(move || {
let mut contador = estado_clone.lock().unwrap();
*contador += 1; // Mutação segura protegida por lock
}));
}
for t in threads { t.join().unwrap(); }
println!("Total sincronizado: {}", *estado_global.lock().unwrap());
⚖️ 6. Matriz Comparativa de Smart Pointers
| Smart Pointer | Memória | Donos | Checagem de Mutabilidade | Thread-Safe? |
|---|---|---|---|---|
Box<T> |
Heap | Único | Tempo de Compilação | SIM (se T: Send) |
Rc<T> |
Heap | Múltiplos | Tempo de Compilação (Imutável) | ❌ NÃO |
Arc<T> |
Heap | Múltiplos | Tempo de Compilação (Imutável) | 🟢 SIM |
RefCell<T> |
Heap/Stack | Único | Tempo de Execução (Runtime) | ❌ NÃO |
Mutex<T> |
Heap/Stack | Único | Bloqueio de Lock em Runtime | 🟢 SIM |
⚠️ 7. Armadilhas Comuns e Mensagens do Compilador
- Tentativa de Enviar
Rc<T>entre Threads (E0277):- O compilador acusará: ``Rc
` cannot be sent between threads safely`. - Correção: Substitua
Rc<T>porArc<T>.
- O compilador acusará: ``Rc
- Deadlocks com Múltiplos Mutexes:
Se a Thread A trava
Mutex1e aguardaMutex2, enquanto a Thread B travaMutex2e aguardaMutex1, o sistema congela permanentemente em deadlock. Adquira locks sempre na mesma ordem em todas as rotinas.
🛡️ 8. Prevenção de Memory Leaks: Weak<T>
Grafos cíclicos criados com Rc ou Arc onde o Nó A aponta para o Nó B e o Nó B aponta de volta para o Nó A farão com que o contador de referências nunca chegue a zero, criando vazamentos de memória (Memory Leaks). Para quebrar ciclos, utilize Weak<T> (referência fraca que não incrementa o contador proprietário).
📋 9. Cheat-Sheet: Resumo Executivo
// Box: Alocação simples
let b = Box::new(100);
// Single-Thread Múltiplos Donos
let rc = Rc::new(100);
let r2 = Rc::clone(&rc);
// Multithread com Mutação Segura
let shared = Arc::new(Mutex::new(0));
*shared.lock().unwrap() += 1;