O que são Rc<T> e Arc<T>
Rc<T> (Reference Counted) e Arc<T> (Atomically Reference Counted) são smart pointers que permitem múltiplos donos para o mesmo valor. Cada vez que você clona um Rc ou Arc, apenas o contador de referências é incrementado — os dados não são copiados. Quando o último dono é liberado (o contador chega a zero), a memória é desalocada automaticamente.
A diferença crucial entre eles é: Rc<T> funciona apenas em single-thread (não implementa Send nem Sync), enquanto Arc<T> usa operações atômicas no contador, tornando-o seguro para uso entre threads. O Arc tem um pequeno custo extra de performance por causa da sincronização atômica. Use Rc quando possível e Arc apenas quando precisar compartilhar dados entre threads.
Criando Rc e Arc
use std::rc::Rc;
use std::sync::Arc;
fn main() {
// Rc<T> — single thread
let rc1 = Rc::new(String::from("compartilhado"));
let rc2 = Rc::clone(&rc1); // Incrementa contador, NÃO clona a String
let rc3 = rc1.clone(); // Equivalente a Rc::clone
println!("Valor: {rc1}");
println!("Referências: {}", Rc::strong_count(&rc1)); // 3
drop(rc3);
println!("Após drop: {}", Rc::strong_count(&rc1)); // 2
// Arc<T> — thread-safe
let arc1 = Arc::new(vec![1, 2, 3]);
let arc2 = Arc::clone(&arc1);
println!("Arc refs: {}", Arc::strong_count(&arc1)); // 2
println!("Dados: {arc2:?}");
}
Tabela de Métodos Principais
Rc<T>
| Método | Descrição | Exemplo |
|---|---|---|
Rc::new(val) | Cria um novo Rc | Rc::new(42) |
Rc::clone(&rc) | Incrementa a contagem (sem cópia de dados) | Rc::clone(&meu_rc) |
Rc::strong_count(&rc) | Número de referências fortes | Rc::strong_count(&rc) → 3 |
Rc::weak_count(&rc) | Número de referências fracas | Rc::weak_count(&rc) → 1 |
Rc::downgrade(&rc) | Cria uma referência fraca (Weak<T>) | Rc::downgrade(&rc) |
Rc::try_unwrap(rc) | Extrai o valor se há exatamente 1 ref | Rc::try_unwrap(rc).ok() |
Rc::into_inner(rc) | Extrai se é o último dono (Rust 1.70+) | Rc::into_inner(rc) |
Rc::ptr_eq(&a, &b) | Compara ponteiros (mesma alocação?) | Rc::ptr_eq(&rc1, &rc2) |
Arc<T>
| Método | Descrição | Exemplo |
|---|---|---|
Arc::new(val) | Cria um novo Arc | Arc::new(42) |
Arc::clone(&arc) | Incrementa a contagem atomicamente | Arc::clone(&meu_arc) |
Arc::strong_count(&arc) | Número de referências fortes | Arc::strong_count(&arc) |
Arc::weak_count(&arc) | Número de referências fracas | Arc::weak_count(&arc) |
Arc::downgrade(&arc) | Cria Weak<T> | Arc::downgrade(&arc) |
Arc::try_unwrap(arc) | Extrai se há exatamente 1 ref | Arc::try_unwrap(arc).ok() |
Arc::ptr_eq(&a, &b) | Compara ponteiros | Arc::ptr_eq(&a1, &a2) |
Weak<T>
| Método | Descrição | Exemplo |
|---|---|---|
weak.upgrade() | Tenta obter Option<Rc<T>> ou Option<Arc<T>> | weak.upgrade() |
weak.strong_count() | Contagem de refs fortes | weak.strong_count() |
Exemplos Práticos
1. Compartilhando Dados entre Estruturas
use std::rc::Rc;
#[derive(Debug)]
struct Cidade {
nome: String,
populacao: u64,
}
#[derive(Debug)]
struct Empresa {
nome: String,
sede: Rc<Cidade>,
}
#[derive(Debug)]
struct Universidade {
nome: String,
cidade: Rc<Cidade>,
}
fn main() {
let sp = Rc::new(Cidade {
nome: "São Paulo".into(),
populacao: 12_300_000,
});
let empresa = Empresa {
nome: "TechBR".into(),
sede: Rc::clone(&sp),
};
let uni = Universidade {
nome: "USP".into(),
cidade: Rc::clone(&sp),
};
println!("{} tem sede em {}", empresa.nome, empresa.sede.nome);
println!("{} fica em {}", uni.nome, uni.cidade.nome);
println!("Referências a SP: {}", Rc::strong_count(&sp)); // 3
// Comparação de ponteiros — são o MESMO objeto
assert!(Rc::ptr_eq(&empresa.sede, &uni.cidade));
}
2. Referências Fracas para Evitar Ciclos
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct No {
valor: i32,
filhos: RefCell<Vec<Rc<No>>>,
pai: RefCell<Weak<No>>,
}
impl No {
fn novo(valor: i32) -> Rc<Self> {
Rc::new(No {
valor,
filhos: RefCell::new(Vec::new()),
pai: RefCell::new(Weak::new()),
})
}
fn adicionar_filho(pai: &Rc<No>, filho: &Rc<No>) {
pai.filhos.borrow_mut().push(Rc::clone(filho));
*filho.pai.borrow_mut() = Rc::downgrade(pai);
}
}
fn main() {
let raiz = No::novo(1);
let filho_a = No::novo(2);
let filho_b = No::novo(3);
No::adicionar_filho(&raiz, &filho_a);
No::adicionar_filho(&raiz, &filho_b);
// Navegar de filho para pai
if let Some(pai) = filho_a.pai.borrow().upgrade() {
println!("Pai do nó {}: {}", filho_a.valor, pai.valor); // Pai do nó 2: 1
}
println!("Raiz refs fortes: {}", Rc::strong_count(&raiz)); // 1
println!("Raiz refs fracas: {}", Rc::weak_count(&raiz)); // 2
// Sem Weak, haveria um ciclo pai→filho→pai que impediria a desalocação
}
3. Arc + Mutex para Dados Compartilhados entre Threads
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let contador = Arc::new(Mutex::new(0));
let mut handles = vec![];
for i in 0..10 {
let contador = Arc::clone(&contador);
let handle = thread::spawn(move || {
let mut num = contador.lock().unwrap();
*num += 1;
println!("Thread {i}: contador = {num}");
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Resultado final: {}", *contador.lock().unwrap()); // 10
}
4. Cache Compartilhado com Arc + RwLock
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::thread;
type Cache = Arc<RwLock<HashMap<String, String>>>;
fn buscar_com_cache(cache: &Cache, chave: &str) -> String {
// Tenta ler primeiro (múltiplas threads podem ler simultaneamente)
{
let leitura = cache.read().unwrap();
if let Some(valor) = leitura.get(chave) {
return valor.clone();
}
} // Lock de leitura liberado aqui
// Se não encontrou, simula busca e salva no cache
let valor = format!("resultado_para_{chave}");
{
let mut escrita = cache.write().unwrap();
escrita.insert(chave.to_string(), valor.clone());
}
valor
}
fn main() {
let cache: Cache = Arc::new(RwLock::new(HashMap::new()));
let mut handles = vec![];
let chaves = vec!["user:1", "user:2", "user:1", "user:3", "user:2"];
for chave in chaves {
let cache = Arc::clone(&cache);
let chave = chave.to_string();
let handle = thread::spawn(move || {
let resultado = buscar_com_cache(&cache, &chave);
println!("[{chave}] → {resultado}");
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let leitura = cache.read().unwrap();
println!("\nCache final: {} entradas", leitura.len());
for (k, v) in leitura.iter() {
println!(" {k}: {v}");
}
}
5. Padrão Observer com Rc e Weak
use std::cell::RefCell;
use std::rc::{Rc, Weak};
trait Observador {
fn notificar(&self, mensagem: &str);
}
struct EventBus {
ouvintes: RefCell<Vec<Weak<dyn Observador>>>,
}
impl EventBus {
fn novo() -> Self {
EventBus {
ouvintes: RefCell::new(Vec::new()),
}
}
fn registrar(&self, ouvinte: &Rc<dyn Observador>) {
self.ouvintes.borrow_mut().push(Rc::downgrade(ouvinte));
}
fn emitir(&self, mensagem: &str) {
let mut ouvintes = self.ouvintes.borrow_mut();
// Remove ouvintes que já foram desalocados e notifica os ativos
ouvintes.retain(|weak| {
if let Some(ouvinte) = weak.upgrade() {
ouvinte.notificar(mensagem);
true
} else {
false // Ouvinte foi liberado, remover da lista
}
});
}
}
struct Logger {
prefixo: String,
}
impl Observador for Logger {
fn notificar(&self, mensagem: &str) {
println!("[{}] {mensagem}", self.prefixo);
}
}
fn main() {
let bus = EventBus::novo();
let logger1: Rc<dyn Observador> = Rc::new(Logger {
prefixo: "INFO".into(),
});
let logger2: Rc<dyn Observador> = Rc::new(Logger {
prefixo: "DEBUG".into(),
});
bus.registrar(&logger1);
bus.registrar(&logger2);
bus.emitir("Sistema iniciado");
// [INFO] Sistema iniciado
// [DEBUG] Sistema iniciado
drop(logger2); // Remove o logger DEBUG
bus.emitir("Processando requisição");
// [INFO] Processando requisição
// (logger2 foi automaticamente removido)
}
Dicas de Performance e Armadilhas
Rc não é thread-safe: Tentar enviar um
Rcentre threads gera erro de compilação. UseArcpara cenários multi-thread.Rc/Arc são imutáveis por padrão: Para mutabilidade interna, combine com
Cell,RefCell(single-thread) ouMutex,RwLock(multi-thread).Ciclos de referência:
RceArcnão detectam ciclos. Se A aponta para B e B aponta para A, a memória nunca é liberada. UseWeakpara quebrar ciclos.Clone é barato, mas não gratuito:
Rc::cloneeArc::cloneincrementam um contador. ParaArc, é uma operação atômica — mais cara queRcmas ainda muito rápida.Prefira
Rc::clone(&rc)arc.clone(): A formaRc::clone(&rc)deixa claro que estamos incrementando a contagem, não clonando os dados. É uma convenção importante em Rust.Arc<Mutex<T>>é o padrão: Para dados compartilhados e mutáveis entre threads,Arc<Mutex<T>>é o padrão mais comum. Para leituras frequentes, considereArc<RwLock<T>>.
Veja Também
- Smart Pointers em Rust — visão geral de todos os smart pointers
- Concorrência em Rust — tutorial de programação concorrente
- Box<T>: Alocação no Heap — smart pointer com dono único
- Cow<T>: Clone on Write — alternativa para evitar clonagens
- Iterator Trait — para processar dados compartilhados
- HashMap<K,V> — frequentemente usado com Arc+Mutex
- Cheatsheet Rust — referência rápida de sintaxe
- Documentação oficial — std::rc::Rc
- Documentação oficial — std::sync::Arc