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:

  1. Deref (std::ops::Deref): Permite tratar a struct do smart pointer como uma referência direta através do operador de desreferenciação *ponteiro e habilita a mágica do Deref Coercion.
  2. 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>:

  1. 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,
    }
    
  2. Transferir Grandes Volumes de Dados sem Cópia na Pilha: Mover um Box copia apenas o ponteiro de 8 bytes na stack, deixando o buffer pesado intacto na Heap.
  3. 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 trait Send. 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 Send e Sync), 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, o RefCell dispara 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 um MutexGuard inteligente que libera a trava automaticamente ao sair de escopo via Drop.

💎 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

  1. Tentativa de Enviar Rc<T> entre Threads (E0277):
    • O compilador acusará: ``Rc` cannot be sent between threads safely`.
    • Correção: Substitua Rc<T> por Arc<T>.
  2. Deadlocks com Múltiplos Mutexes: Se a Thread A trava Mutex1 e aguarda Mutex2, enquanto a Thread B trava Mutex2 e aguarda Mutex1, 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;