---
title: "Smart Pointers Rust: Box, Rc, Arc, RefCell | Rust Brasil"
url: "https://rustlang.com.br/artigos/smart-pointers/"
markdown_url: "https://rustlang.com.br/artigos/smart-pointers.MD"
description: "Guia completo de smart pointers em Rust: Box, Rc, Arc e RefCell. Quando usar, Send/Sync e segurança entre threads."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Smart Pointers Rust: Box, Rc, Arc, RefCell | Rust Brasil

Guia completo de smart pointers em Rust: Box, Rc, Arc e RefCell. Quando usar, Send/Sync e segurança entre threads.


Smart pointers (ponteiros inteligentes) são estruturas de dados que se comportam como ponteiros, mas possuem metadados e capacidades adicionais. Em Rust, eles são fundamentais para cenários onde o sistema de ownership padrão não é suficiente — como estruturas de dados recursivas, contagem de referência ou mutabilidade interior. Neste artigo, vamos explorar cada smart pointer em detalhes, entender como funcionam por dentro e quando usar cada um.

## Por Que Precisamos de Smart Pointers?

O sistema de ownership do Rust é poderoso, mas tem limitações práticas:

- **Tamanho desconhecido em tempo de compilação**: tipos recursivos não têm tamanho fixo
- **Múltiplos donos**: às vezes, vários trechos de código precisam acessar o mesmo dado
- **Mutabilidade controlada**: você pode precisar mutar dados mesmo tendo apenas uma referência imutável

Smart pointers resolvem cada um desses cenários com abstrações seguras e eficientes.

## `Box<T>` — Alocação no Heap

`Box<T>` é o smart pointer mais simples: aloca dados no heap e mantém um ponteiro na stack.

### Layout de Memória

```
   Stack                Heap
┌──────────┐       ┌──────────┐
│ Box<i32>  │──────>│   42     │
│ (ponteiro)│       │ (i32)    │
│ 8 bytes   │       │ 4 bytes  │
└──────────┘       └──────────┘
```

### Quando Usar Box

**1. Tipos recursivos:**

```rust
// NÃO COMPILA — tamanho infinito
// enum Lista {
//     Cons(i32, Lista),
//     Nil,
// }

// COMPILA — Box tem tamanho fixo (1 ponteiro)
#[derive(Debug)]
enum Lista {
    Cons(i32, Box<Lista>),
    Nil,
}

fn main() {
    use Lista::{Cons, Nil};

    let lista = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
    println!("{:?}", lista);
    // Cons(1, Cons(2, Cons(3, Nil)))
}
```

**2. Trait objects com tamanho dinâmico:**

```rust
trait Animal {
    fn som(&self) -> &str;
    fn nome(&self) -> &str;
}

struct Gato;
struct Cachorro;

impl Animal for Gato {
    fn som(&self) -> &str { "Miau" }
    fn nome(&self) -> &str { "Gato" }
}

impl Animal for Cachorro {
    fn som(&self) -> &str { "Au au" }
    fn nome(&self) -> &str { "Cachorro" }
}

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

    for animal in &animais {
        println!("{} faz {}", animal.nome(), animal.som());
    }
}
```

**3. Transferir ownership de dados grandes sem copiar:**

```rust
fn processar(dados: Box<[u8; 1_000_000]>) {
    println!("Processando {} bytes", dados.len());
}

fn main() {
    let dados = Box::new([0u8; 1_000_000]); // 1MB no heap, não na stack
    processar(dados); // move o ponteiro, não os dados
}
```

## `Rc<T>` — Contagem de Referência (Single-Thread)

`Rc<T>` (Reference Counting) permite que múltiplos donos compartilhem ownership de um dado. Um contador interno rastreia quantas referências existem, e o dado só é destruído quando o último `Rc` é descartado.

### Layout de Memória

```
   Stack                    Heap
┌──────────┐           ┌──────────────┐
│ Rc (a)   │──┐        │ strong: 3    │
└──────────┘  │        │ weak: 0      │
              ├───────>│──────────────│
┌──────────┐  │        │ dados: T     │
│ Rc (b)   │──┤        │              │
└──────────┘  │        └──────────────┘
              │
┌──────────┐  │
│ Rc (c)   │──┘
└──────────┘
```

### Exemplo Prático: Grafo Simples

```rust
use std::rc::Rc;

#[derive(Debug)]
struct No {
    valor: i32,
    vizinhos: Vec<Rc<No>>,
}

fn main() {
    let compartilhado = Rc::new(No {
        valor: 1,
        vizinhos: vec![],
    });

    println!("Contagem após criação: {}", Rc::strong_count(&compartilhado));

    let no_a = Rc::new(No {
        valor: 2,
        vizinhos: vec![Rc::clone(&compartilhado)],
    });

    let no_b = Rc::new(No {
        valor: 3,
        vizinhos: vec![Rc::clone(&compartilhado)],
    });

    println!("Contagem após clones: {}", Rc::strong_count(&compartilhado));
    // Contagem após clones: 3

    drop(no_a);
    println!("Contagem após drop de no_a: {}", Rc::strong_count(&compartilhado));
    // Contagem após drop de no_a: 2

    drop(no_b);
    println!("Contagem após drop de no_b: {}", Rc::strong_count(&compartilhado));
    // Contagem após drop de no_b: 1
}
```

### `Rc::clone` vs `.clone()`

`Rc::clone(&rc)` **não** clona os dados — apenas incrementa o contador. É uma operação O(1). Use `Rc::clone()` em vez de `rc.clone()` para deixar clara a intenção.

### `Weak<T>` — Evitando Ciclos

`Rc` pode criar ciclos de referência que causam **memory leaks**. Use `Weak` para referências que não mantêm o dado vivo:

```rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Pessoa {
    nome: String,
    melhor_amigo: RefCell<Option<Weak<Pessoa>>>,
}

impl Drop for Pessoa {
    fn drop(&mut self) {
        println!("{} foi desalocado", self.nome);
    }
}

fn main() {
    let ana = Rc::new(Pessoa {
        nome: "Ana".into(),
        melhor_amigo: RefCell::new(None),
    });

    let carlos = Rc::new(Pessoa {
        nome: "Carlos".into(),
        melhor_amigo: RefCell::new(None),
    });

    // Referências fracas — não criam ciclo
    *ana.melhor_amigo.borrow_mut() = Some(Rc::downgrade(&carlos));
    *carlos.melhor_amigo.borrow_mut() = Some(Rc::downgrade(&ana));

    // Acessando o Weak
    if let Some(amigo) = ana.melhor_amigo.borrow().as_ref() {
        if let Some(amigo_rc) = amigo.upgrade() {
            println!("Melhor amigo de Ana: {}", amigo_rc.nome);
        }
    }
} // Ana e Carlos são desalocados corretamente
```

## `Arc<T>` — Contagem de Referência Thread-Safe

`Arc<T>` (Atomic Reference Counting) funciona como `Rc<T>`, mas usa operações atômicas para ser seguro entre threads. O custo é pequeno, mas mensurável.

```rust
use std::sync::Arc;
use std::thread;

fn main() {
    let dados = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let dados_clone = Arc::clone(&dados);
        let handle = thread::spawn(move || {
            let soma: i32 = dados_clone.iter().sum();
            println!("Thread {}: soma = {}", i, soma);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}
```

### Arc + Mutex — Mutabilidade Compartilhada entre Threads

```rust
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let contador = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let contador_clone = Arc::clone(&contador);
        let handle = thread::spawn(move || {
            let mut num = contador_clone.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Resultado: {}", *contador.lock().unwrap());
    // Resultado: 10
}
```

## `RefCell<T>` — Mutabilidade Interior

`RefCell<T>` permite mutar dados mesmo quando só temos uma referência imutável. As regras de borrowing são verificadas em **tempo de execução** em vez de compilação.

### Regras de Borrowing em Runtime

```
RefCell — verificação em runtime:
┌─────────────────────────────────────────────┐
│ .borrow()     → Ref<T>   (imutável)        │
│ .borrow_mut() → RefMut<T> (mutável)        │
│                                             │
│ Múltiplos .borrow()    → OK                │
│ Um .borrow_mut()       → OK                │
│ .borrow() + .borrow_mut() → PANIC!         │
│ Múltiplos .borrow_mut()   → PANIC!         │
└─────────────────────────────────────────────┘
```

### Exemplo: Cache com Mutabilidade Interior

```rust
use std::cell::RefCell;
use std::collections::HashMap;

struct Cache {
    dados: RefCell<HashMap<String, String>>,
}

impl Cache {
    fn novo() -> Self {
        Cache {
            dados: RefCell::new(HashMap::new()),
        }
    }

    // Note: &self (imutável!), mas consegue mutar o HashMap interno
    fn obter_ou_calcular(&self, chave: &str) -> String {
        // Verifica se já existe
        if let Some(valor) = self.dados.borrow().get(chave) {
            return valor.clone();
        }

        // Calcula e insere
        let valor = format!("calculado_{}", chave);
        self.dados.borrow_mut().insert(chave.to_string(), valor.clone());
        valor
    }
}

fn main() {
    let cache = Cache::novo();

    println!("{}", cache.obter_ou_calcular("x")); // calculado_x
    println!("{}", cache.obter_ou_calcular("y")); // calculado_y
    println!("{}", cache.obter_ou_calcular("x")); // calculado_x (do cache)

    println!("Cache: {:?}", cache.dados.borrow());
}
```

## Combinando Smart Pointers: `Rc<RefCell<T>>`

A combinação `Rc<RefCell<T>>` é extremamente comum — permite múltiplos donos com mutabilidade:

```rust
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Conta {
    titular: String,
    saldo: f64,
}

type ContaCompartilhada = Rc<RefCell<Conta>>;

fn depositar(conta: &ContaCompartilhada, valor: f64) {
    conta.borrow_mut().saldo += valor;
}

fn sacar(conta: &ContaCompartilhada, valor: f64) -> Result<(), String> {
    let mut c = conta.borrow_mut();
    if c.saldo >= valor {
        c.saldo -= valor;
        Ok(())
    } else {
        Err(format!("Saldo insuficiente: {:.2}", c.saldo))
    }
}

fn main() {
    let conta = Rc::new(RefCell::new(Conta {
        titular: "Maria".into(),
        saldo: 1000.0,
    }));

    // Simulando múltiplos "serviços" acessando a mesma conta
    let servico_deposito = Rc::clone(&conta);
    let servico_saque = Rc::clone(&conta);

    depositar(&servico_deposito, 500.0);
    println!("Após depósito: {:.2}", conta.borrow().saldo); // 1500.00

    sacar(&servico_saque, 200.0).unwrap();
    println!("Após saque: {:.2}", conta.borrow().saldo); // 1300.00
}
```

## Tabela Comparativa

```
┌──────────────┬──────────┬──────────────┬──────────────┬────────────┐
│ Tipo         │ Heap?    │ Múltiplos    │ Mutabilidade │ Thread-    │
│              │          │ Donos?       │ Interior?    │ Safe?      │
├──────────────┼──────────┼──────────────┼──────────────┼────────────┤
│ Box<T>       │ Sim      │ Não          │ Não          │ Sim*       │
│ Rc<T>        │ Sim      │ Sim          │ Não          │ Não        │
│ Arc<T>       │ Sim      │ Sim          │ Não          │ Sim        │
│ RefCell<T>   │ Não**    │ Não          │ Sim          │ Não        │
│ Mutex<T>     │ Não**    │ Não          │ Sim          │ Sim        │
│ Rc<RefCell>  │ Sim      │ Sim          │ Sim          │ Não        │
│ Arc<Mutex>   │ Sim      │ Sim          │ Sim          │ Sim        │
└──────────────┴──────────┴──────────────┴──────────────┴────────────┘

* Box<T> é Send/Sync se T for Send/Sync
** RefCell/Mutex não alocam no heap por si — dependem de onde são criados
```

## Erros Comuns

### 1. Usar Rc entre threads

```rust
use std::rc::Rc;
// use std::thread;

// NÃO COMPILA: Rc não é Send
// let dados = Rc::new(42);
// thread::spawn(move || println!("{}", dados));

// SOLUÇÃO: Use Arc
use std::sync::Arc;
use std::thread;

fn main() {
    let dados = Arc::new(42);
    let dados_clone = Arc::clone(&dados);
    thread::spawn(move || println!("{}", dados_clone)).join().unwrap();
}
```

### 2. Panic por borrow duplo em RefCell

```rust
use std::cell::RefCell;

fn main() {
    let dados = RefCell::new(vec![1, 2, 3]);

    let r1 = dados.borrow();
    // let r2 = dados.borrow_mut(); // PANIC em runtime!

    // SOLUÇÃO: Garanta que o borrow anterior termine antes
    drop(r1);
    let r2 = dados.borrow_mut();
    println!("{:?}", r2);

    // Ou use try_borrow_mut para evitar panic
    drop(r2);
    let _r3 = dados.borrow();
    match dados.try_borrow_mut() {
        Ok(mut r) => r.push(4),
        Err(e) => println!("Não foi possível emprestar: {}", e),
    }
}
```

### 3. Memory leak com ciclos em Rc

Se dois `Rc` apontam um para o outro, nenhum deles será desalocado. Use `Weak` para quebrar ciclos (como mostrado na seção sobre `Weak<T>`).

## Aplicações no Mundo Real

### Árvore com nós compartilhados

```rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct TreeNode {
    valor: i32,
    pai: RefCell<Weak<TreeNode>>,
    filhos: RefCell<Vec<Rc<TreeNode>>>,
}

fn main() {
    let raiz = Rc::new(TreeNode {
        valor: 1,
        pai: RefCell::new(Weak::new()),
        filhos: RefCell::new(vec![]),
    });

    let filho = Rc::new(TreeNode {
        valor: 2,
        pai: RefCell::new(Rc::downgrade(&raiz)),
        filhos: RefCell::new(vec![]),
    });

    raiz.filhos.borrow_mut().push(Rc::clone(&filho));

    println!("Raiz: {}", raiz.valor);
    println!("Filho: {}", filho.valor);

    if let Some(pai) = filho.pai.borrow().upgrade() {
        println!("Pai do filho: {}", pai.valor);
    }
}
```

## Veja Também

- [Ownership e Borrowing: O Coração do Rust](/tutoriais/ownership-borrowing/) — base para entender por que smart pointers existem
- [Entendendo Lifetimes em Rust](/artigos/lifetimes-em-profundidade/) — alternativas com referências e lifetimes
- [Trait Objects vs Generics em Rust](/artigos/trait-objects-vs-generics/) — `Box<dyn Trait>` em detalhe
- [Async/Await em Rust](/artigos/async-await-profundidade/) — `Arc<Mutex<T>>` em contextos assíncronos
- [Glossário Rust em Português](/glossario/) — definições de termos técnicos

---

Se você se interessa por gerenciamento de memória em outras linguagens, confira também:

- <a href="https://ziglang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'ziglang.com.br' })">Modelo de memória em Zig: allocators explícitos e controle manual sem garbage collector</a>
- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Garbage Collector em Go: como Go gerencia memória automaticamente</a>
