Rust

Dominando Rc<T> e Arc<T> em Rust: Contagem de Referências em Projetos Reais

9 min de leitura

Dominando Rc<T> e Arc<T> em Rust: Contagem de Referências em Projetos Reais

Rc : Contagem de Referências em Single-Thread (Reference Counting) é um tipo de dado que permite múltiplos proprietários para um mesmo valor em um programa single-thread. Diferente do sistema de ownership padrão do Rust, onde apenas um dono é permitido, mantém um contador interno que rastreia quantas referências imutáveis apontam para os dados. Quando o contador chega a zero, a memória é automaticamente liberada. O caso de uso clássico é quando você precisa compartilhar dados entre múltiplas partes do código sem transferir ownership. Considere uma estrutura de árvore onde múltiplos nós precisam apontar para o mesmo nó pai: é exatamente o cenário onde brilha. A desvantagem é que você só consegue referências imutáveis; se precisar modificar dados, deve combinar com . Por que não usar em Multi-thread? usa operações não-atômicas para incrementar/decrementar o contador. Em um ambiente multi-thread, múltiplas threads poderiam modificar o contador simultaneamente, causando race conditions. O compilador Rust impede isso: não implementa nem , então você simplesmente

<h2>Rc&lt;T&gt;: Contagem de Referências em Single-Thread</h2>

<p><code>Rc&lt;T&gt;</code> (Reference Counting) é um tipo de dado que permite múltiplos proprietários para um mesmo valor em um programa single-thread. Diferente do sistema de ownership padrão do Rust, onde apenas um dono é permitido, <code>Rc&lt;T&gt;</code> mantém um contador interno que rastreia quantas referências imutáveis apontam para os dados. Quando o contador chega a zero, a memória é automaticamente liberada.</p>

<p>O caso de uso clássico é quando você precisa compartilhar dados entre múltiplas partes do código sem transferir ownership. Considere uma estrutura de árvore onde múltiplos nós precisam apontar para o mesmo nó pai: é exatamente o cenário onde <code>Rc&lt;T&gt;</code> brilha. A desvantagem é que você só consegue referências imutáveis; se precisar modificar dados, deve combinar com <code>RefCell&lt;T&gt;</code>.</p>

<pre><code class="language-rust">use std::rc::Rc;

struct Node {

value: i32,

children: Vec&lt;Rc&lt;Node&gt;&gt;,

}

impl Node {

fn new(value: i32) -&gt; Self {

Node {

value,

children: Vec::new(),

}

}

}

fn main() {

let shared_node = Rc::new(Node::new(42));

// Clone cria uma nova referência, não copia os dados

let ref1 = Rc::clone(&amp;shared_node);

let ref2 = Rc::clone(&amp;shared_node);

println!(&quot;Contador de referências: {}&quot;, Rc::strong_count(&amp;shared_node));

// Saída: 3 (shared_node + ref1 + ref2)

drop(ref1);

println!(&quot;Após drop: {}&quot;, Rc::strong_count(&amp;shared_node));

// Saída: 2

}</code></pre>

<h3>Por que não usar <code>Rc&lt;T&gt;</code> em Multi-thread?</h3>

<p><code>Rc&lt;T&gt;</code> usa operações não-atômicas para incrementar/decrementar o contador. Em um ambiente multi-thread, múltiplas threads poderiam modificar o contador simultaneamente, causando race conditions. O compilador Rust impede isso: <code>Rc&lt;T&gt;</code> não implementa <code>Send</code> nem <code>Sync</code>, então você simplesmente não consegue compartilhá-lo entre threads.</p>

<h2>Arc&lt;T&gt;: Atomic Reference Counting para Multi-thread</h2>

<p><code>Arc&lt;T&gt;</code> (Atomic Reference Counting) é a versão thread-safe de <code>Rc&lt;T&gt;</code>. Usa operações atômicas para gerenciar o contador, garantindo que múltiplas threads possam acessar o mesmo dado simultaneamente sem race conditions. A troca é um pequeno overhead de performance, compensado pela segurança.</p>

<p><code>Arc&lt;T&gt;</code> é essencial quando você precisa compartilhar dados entre threads de forma segura. Um padrão comum é usá-lo com <code>Mutex&lt;T&gt;</code> para permitir modificações thread-safe. Sozinho, <code>Arc&lt;T&gt;</code> oferece apenas referências imutáveis, mas a combinação <code>Arc&lt;Mutex&lt;T&gt;&gt;</code> é poderosa: você consegue propriedade compartilhada com acesso exclusivo para modificação.</p>

<pre><code class="language-rust">use std::sync::Arc;

use std::thread;

fn main() {

let counter = Arc::new(0);

let mut handles = vec![];

for _ in 0..3 {

let counter_clone = Arc::clone(&amp;counter);

let handle = thread::spawn(move || {

println!(&quot;Thread com contador: {:?}&quot;, counter_clone);

});

handles.push(handle);

}

for handle in handles {

handle.join().unwrap();

}

}</code></pre>

<h3>Arc&lt;T&gt; com Mutex&lt;T&gt;</h3>

<p>Para modificar dados compartilhados entre threads, combine <code>Arc&lt;Mutex&lt;T&gt;&gt;</code>. O <code>Mutex</code> garante que apenas uma thread acessa os dados por vez, enquanto <code>Arc</code> permite que múltiplas threads possuam a referência.</p>

<pre><code class="language-rust">use std::sync::{Arc, Mutex};

use std::thread;

fn main() {

let counter = Arc::new(Mutex::new(0));

let mut handles = vec![];

for _ in 0..5 {

let counter_clone = Arc::clone(&amp;counter);

let handle = thread::spawn(move || {

let mut num = counter_clone.lock().unwrap();

*num += 1;

});

handles.push(handle);

}

for handle in handles {

handle.join().unwrap();

}

println!(&quot;Resultado: {}&quot;, *counter.lock().unwrap());

// Saída: 5

}</code></pre>

<h2>Comparação Prática: Quando Usar Cada Um</h2>

<p><code>Rc&lt;T&gt;</code> é a escolha para programas single-thread onde você precisa de múltiplos donos. Sua implementação é mais eficiente porque não requer atomicidade. Use quando: estruturas de dados complexas (grafos, árvores), caching compartilhado ou componentes que precisam acessar um recurso comum sem transferência de ownership.</p>

<p><code>Arc&lt;T&gt;</code> é obrigatório em multi-threading. O overhead de operações atômicas é negligenciável comparado aos benefícios de segurança. Use quando: compartilhar estado entre threads, implementar workers em thread pools ou quando dados precisam ser acessíveis de múltiplos lugares simultaneamente em execução paralela.</p>

<div class="table-wrap"><table><thead><tr><th>Aspecto</th><th>Rc&lt;T&gt;</th><th>Arc&lt;T&gt;</th></tr></thead><tbody><tr><td>Single-thread</td><td>✓</td><td>✓</td></tr><tr><td>Multi-thread</td><td>✗</td><td>✓</td></tr><tr><td>Overhead</td><td>Mínimo</td><td>Atomicidade</td></tr><tr><td>Segurança em paralelo</td><td>Nenhuma</td><td>Total</td></tr><tr><td>Caso de uso</td><td>Compartilhamento local</td><td>Compartilhamento global</td></tr></tbody></table></div>

<pre><code class="language-rust">// Rc para single-thread: grafo de dependências

use std::rc::Rc;

struct Package {

name: String,

depends_on: Vec&lt;Rc&lt;Package&gt;&gt;,

}

// Arc para multi-thread: processamento distribuído

use std::sync::Arc;

use std::thread;

let data = Arc::new(vec![1, 2, 3, 4, 5]);

let handles: Vec&lt;_&gt; = (0..4).map(|i| {

let d = Arc::clone(&amp;data);

thread::spawn(move || {

println!(&quot;Thread {} vê: {:?}&quot;, i, d);

})

}).collect();</code></pre>

<h2>Conclusão</h2>

<p><code>Rc&lt;T&gt;</code> e <code>Arc&lt;T&gt;</code> resolvem um dos maiores desafios em linguagens de baixo nível: compartilhar dados mantendo segurança de memória. <strong>Primeiro ponto</strong>: <code>Rc&lt;T&gt;</code> é para single-thread e usa contagem não-atômica; <code>Arc&lt;T&gt;</code> é para multi-thread com contagem atômica. <strong>Segundo ponto</strong>: ambos oferecem apenas referências imutáveis por padrão—combine com <code>RefCell&lt;T&gt;</code> ou <code>Mutex&lt;T&gt;</code> para mutabilidade. <strong>Terceiro ponto</strong>: escolha baseado em seu contexto (threads ou não) e o compilador Rust o guiará; ele impede misturar tipos inadequados em tempo de compilação.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/book/ch15-00-smart-pointers.html" target="_blank" rel="noopener noreferrer">The Rust Programming Language - Smart Pointers</a></li>

<li><a href="https://doc.rust-lang.org/std/rc/struct.Rc.html" target="_blank" rel="noopener noreferrer">Rust std::rc::Rc Documentation</a></li>

<li><a href="https://doc.rust-lang.org/std/sync/struct.Arc.html" target="_blank" rel="noopener noreferrer">Rust std::sync::Arc Documentation</a></li>

<li><a href="https://doc.rust-lang.org/book/ch16-00-concurrency.html" target="_blank" rel="noopener noreferrer">Fearless Concurrency - The Rust Book</a></li>

<li><a href="https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/" target="_blank" rel="noopener noreferrer">Programming Rust: Fast, Safe Systems Development - Jim Blandy &amp; Jason Orendorff</a></li>

</ul>

Comentários

Mais em Rust

Como Usar Structs em Rust: Definição, Instanciação e Métodos em Produção
Como Usar Structs em Rust: Definição, Instanciação e Métodos em Produção

Definição e Conceitos Fundamentais Structs (estruturas) são um dos pilares da...

Guia Completo de Unsafe Rust: Quando e Como Usar com Responsabilidade
Guia Completo de Unsafe Rust: Quando e Como Usar com Responsabilidade

Entendendo Unsafe Rust Unsafe Rust é um subconjunto da linguagem que permite...

Guia Completo de Stack vs Heap em Rust: Como a Memória é Gerenciada
Guia Completo de Stack vs Heap em Rust: Como a Memória é Gerenciada

Stack vs Heap em Rust: Como a Memória é Gerenciada O que são Stack e Heap? St...