Rc e Arc: múltiplos owners

1. O Problema do Ownership Único

No modelo de ownership padrão do Rust, cada valor tem exatamente um dono. Quando o dono sai de escopo, o valor é dropado. Isso é ótimo para segurança de memória, mas limitante para certos cenários.

Considere um grafo onde múltiplos nós precisam referenciar um mesmo nó central, ou uma árvore onde um nó filho tem dois pais. Com ownership único, seria impossível representar essas estruturas sem cópias profundas ou lifetimes complexos.

Para resolver isso, Rust oferece Rc<T> (Reference Counting) e sua versão thread-safe Arc<T>. Ambos permitem que um valor tenha múltiplos donos através de contagem de referências (reference counting) — um contador no heap que rastreia quantas referências ativas existem para o valor.

2. Rc: Reference Counting Single-threaded

Rc<T> é um ponteiro inteligente que conta referências de forma não-atômica, adequado apenas para cenários single-threaded. Internamente, Rc<T> aloca o valor no heap junto com dois contadores: strong_count (referências fortes) e weak_count (referências fracas, que veremos depois).

A diferença crucial entre Rc::clone() e o clone() tradicional é que Rc::clone() apenas incrementa o contador de referências, sem copiar o valor subjacente. É uma operação barata.

use std::rc::Rc;

let valor = Rc::new(42);
let ref1 = Rc::clone(&valor);  // Apenas incrementa contador
let ref2 = valor.clone();      // Equivalente, também barato

println!("Contagem: {}", Rc::strong_count(&valor)); // 3

// Acesso ao valor via Deref
println!("Valor: {}", *ref1);
println!("Valor: {}", ref2.as_ref());

3. Rc na Prática: Exemplos e Padrões

Árvore Binária com Múltiplos Pais

use std::rc::Rc;

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

fn main() {
    let folha = Rc::new(No {
        valor: 3,
        filhos: vec![],
    });

    let no_a = Rc::new(No {
        valor: 1,
        filhos: vec![Rc::clone(&folha)],
    });

    let no_b = Rc::new(No {
        valor: 2,
        filhos: vec![Rc::clone(&folha)],
    });

    println!("no_a: {:?}", no_a);
    println!("no_b: {:?}", no_b);
    println!("Contagem da folha: {}", Rc::strong_count(&folha)); // 3
}

Evitando Ciclos com Weak

Ciclos de referência são perigosos porque criam vazamentos de memória — os valores nunca são dropados. Weak<T> é uma referência que não aumenta o strong_count, permitindo que o valor seja dropado mesmo quando referências fracas existem.

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

#[derive(Debug)]
struct Pessoa {
    nome: String,
    pais: RefCell<Vec<Rc<Pessoa>>>,
    filhos: RefCell<Vec<Weak<Pessoa>>>, // Referências fracas para evitar ciclo
}

fn main() {
    let mae = Rc::new(Pessoa {
        nome: "Maria".to_string(),
        pais: RefCell::new(vec![]),
        filhos: RefCell::new(vec![]),
    });

    let filho = Rc::new(Pessoa {
        nome: "João".to_string(),
        pais: RefCell::new(vec![Rc::clone(&mae)]),
        filhos: RefCell::new(vec![]),
    });

    // Adiciona referência fraca do filho na mãe
    mae.filhos.borrow_mut().push(Rc::downgrade(&filho));

    println!("Mãe: {:?}", mae);
    println!("Strong count do filho: {}", Rc::strong_count(&filho)); // 1
    println!("Weak count do filho: {}", Rc::weak_count(&filho));    // 1
}

4. Arc: Reference Counting Thread-safe

Arc<T> (Atomic Reference Counting) usa operações atômicas para garantir segurança em ambientes multi-threaded. O custo é um overhead de desempenho comparado a Rc<T>.

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);
        handles.push(thread::spawn(move || {
            println!("Thread {}: {:?}", i, dados_clone);
        }));
    }

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

    println!("Contagem final: {}", Arc::strong_count(&dados)); // 1 (thread principal)
}

5. Combinação com Mutex e RwLock para Mutabilidade

Tanto Rc quanto Arc só permitem acesso imutável ao valor subjacente. Para mutabilidade compartilhada, precisamos combiná-los com RefCell (single-threaded) ou Mutex/RwLock (multi-threaded).

Arc>: Mutabilidade Segura entre Threads

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);
        handles.push(thread::spawn(move || {
            let mut num = contador_clone.lock().unwrap();
            *num += 1;
        }));
    }

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

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

Arc>: Leitura Concorrente com Escrita Exclusiva

use std::sync::{Arc, RwLock};
use std::thread;

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

    // Leitores concorrentes
    for _ in 0..5 {
        let cache_clone = Arc::clone(&cache);
        handles.push(thread::spawn(move || {
            let leitura = cache_clone.read().unwrap();
            println!("Leitura: {:?}", *leitura);
        }));
    }

    // Escritor exclusivo
    let cache_clone = Arc::clone(&cache);
    handles.push(thread::spawn(move || {
        let mut escrita = cache_clone.write().unwrap();
        escrita.push(4);
    }));

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

6. Comparação e Boas Práticas

Característica Rc Arc
Thread safety Não Sim
Performance Alta Média (operações atômicas)
Uso típico Single-threaded Multi-threaded
Combinação com mutabilidade Rc> Arc>

Quando usar cada um:
- Use Rc em código exclusivamente single-threaded para melhor performance
- Use Arc quando precisar compartilhar entre threads
- Prefira Box<T> ou empréstimos (&T, &mut T) quando possível — são mais simples e performáticos

Armadilhas comuns:
- Ciclos de referência: Use Weak<T> para quebrar ciclos
- Vazamentos de memória: Ciclos com Rc sem Weak nunca são dropados
- RefCell dentro de Rc: Pode causar panics em runtime se violar regras de empréstimo
- Clone desnecessários: Rc::clone() é barato, mas ainda tem custo atômico em Arc

Dica de performance: Em código single-threaded, Rc é significativamente mais rápido que Arc. Use Rc sempre que possível e reserve Arc apenas para cenários que realmente precisam de concorrência.

Referências