Gerenciamento de tempo de vida de objetos em Rust: segredos do coletor de lixo

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que o "coletor de lixo" em Rust não é o que você pensa

\n\n

Se você vem do mundo Python, Java ou C#, a palavra "coletor de lixo" (garbage collector) evoca uma imagem clara: um ambiente em segundo plano que periodicamente encontra e remove objetos aos quais ninguém mais faz referência. Em Rust é diferente. Aqui não há GC clássico, mas existe um sistema de ownership e borrowing que resolve a mesma tarefa — gerenciamento de tempo de vida de objetos — em tempo de compilação.

\n\n

No entanto, a vida (e o código) é mais complexa que esquemas ideais. Às vezes você precisa compartilhar a posse de um objeto entre várias partes do programa, criar referências cíclicas ou trabalhar com dados mutáveis através de ponteiros imutáveis. É aqui que entram em cena os objetos garbage-collected em Rust: Rc, Arc, Weak e RefCell. Vamos entender como gerenciar seu tempo de vida, evitar vazamentos de memória e não quebrar a cabeça.

\n\n

Este artigo é um guia detalhado para quem quer entender como funciona o gerenciamento de tempo de vida de objetos em Rust na prática. Não vamos apenas listar os tipos, mas mostrar cenários reais, incluindo uma pergunta popular do Stack Overflow (sim, aquela com milhares de visualizações).

\n\n

Conceitos-chave: ownership, borrowing e contagem de referências

\n\n

Antes de mergulhar fundo, vamos relembrar a base:

\n\n
    \n
  • Ownership (posse) — cada valor tem exatamente um dono. Quando o dono sai de escopo, o valor é removido.
  • \n
  • Borrowing (empréstimo) — você pode temporariamente "pegar emprestada" uma referência a um valor, sem se tornar dono. Limitações: ou uma referência mutável, ou quantas referências imutáveis quiser.
  • \n
  • Contagem de referências (reference counting) — é um mecanismo que permite ter vários donos de um mesmo valor. Em Rust, isso é implementado através de Rc (para programas single-thread) e Arc (para multi-thread).
  • \n
\n\n

Quando falamos em "objetos garbage-collected" no contexto de Rust, nos referimos exatamente a objetos gerenciados por contagem de referências. Eles não são removidos imediatamente após sair de escopo, mas esperam até que o contador chegue a zero. Isso é o gerenciamento de tempo de vida — você controla quando a memória será liberada.

\n\n

Rc e Arc: a base do gerenciamento de tempo de vida

\n\n

Rc (Reference Counted) é um ponteiro inteligente que permite compartilhar a posse de um valor entre várias partes do código em uma única thread. Cada vez que você clona um Rc, o contador aumenta. Quando o clone sai de escopo — diminui. Quando o contador chega a zero — o valor é removido.

\n\n

Exemplo simples de uso do Rc:

\n\n
use std::rc::Rc;\n\nfn main() {\n    let data = Rc::new(vec![1, 2, 3]);\n    let a = Rc::clone(&data);\n    let b = Rc::clone(&data);\n\n    println!("Contagem de referências: {}", Rc::strong_count(&data)); // 3\n\n    drop(a);\n    println!("Após remover a: {}", Rc::strong_count(&data)); // 2\n}\n
\n\n

Aqui, data, a e b são três donos do mesmo vetor. Enquanto pelo menos um deles estiver vivo, o vetor permanece na memória. Isso é ideal para situações em que você não consegue determinar qual módulo terminará por último.

\n\n

Arc (Atomic Reference Counted) funciona da mesma forma, mas usa operações atômicas, tornando-o seguro para programas multi-thread. O custo é uma pequena redução de desempenho.

\n\n
use std::sync::Arc;\nuse std::thread;\n\nfn main() {\n    let data = Arc::new(vec![1, 2, 3]);\n    let mut handles = vec![];\n\n    for _ in 0..3 {\n        let data_clone = Arc::clone(&data);\n        handles.push(thread::spawn(move || {\n            println!("{:?}", data_clone);\n        }));\n    }\n\n    for handle in handles {\n        handle.join().unwrap();\n    }\n}\n
\n\n

Blogs

Book Recommendations