RAII (Resource Acquisition Is Initialization) é um dos padrões mais fundamentais em Rust — e diferente de C++, onde nasceu, em Rust ele é garantido pelo compilador. A ideia é simples: vincular o tempo de vida de um recurso (arquivo, conexão, lock, memória) ao tempo de vida de um objeto. Quando o objeto sai de escopo, o recurso é liberado automaticamente através da trait Drop.
Em Rust, RAII não é opcional ou uma boa prática — é a forma como a linguagem funciona. Variáveis são destruídas ao sair de escopo, e Drop::drop() é chamado automaticamente. Combinado com o sistema de ownership, isso elimina vazamentos de recursos e garante cleanup determinístico sem garbage collector.
Problema
Gerenciamento manual de recursos é uma fonte interminável de bugs:
// PERIGOSO: gerenciamento manual de recursos
fn processar_dados() -> Result<(), String> {
let arquivo = abrir_arquivo("dados.txt")?;
let conexao = conectar_banco("postgres://...")?;
let lock = adquirir_lock("recurso_compartilhado")?;
// Se ocorrer um erro aqui, recursos não são liberados!
let dados = ler_dados(&arquivo)?; // Se falhar aqui...
salvar_no_banco(&conexao, &dados)?; // ...conexão vaza!
// Cleanup manual — fácil de esquecer
liberar_lock(lock);
fechar_conexao(conexao);
fechar_arquivo(arquivo);
Ok(())
}
Problemas: se um erro ocorre no meio, os recursos anteriores vazam. Se um return antecipado é adicionado, o cleanup é pulado. E com muitos recursos, a ordem de liberação se torna um pesadelo.
Solução em Rust
Trait Drop Basica
/// Recurso que precisa de cleanup ao ser destruído
struct Conexao {
endereco: String,
ativa: bool,
}
impl Conexao {
fn nova(endereco: &str) -> Self {
println!("[CONEXÃO] Abrindo conexão com {}", endereco);
Conexao {
endereco: endereco.to_string(),
ativa: true,
}
}
fn executar_query(&self, sql: &str) -> Result<Vec<String>, String> {
if !self.ativa {
return Err("Conexão fechada".to_string());
}
println!("[QUERY] Executando: {}", sql);
Ok(vec!["resultado1".to_string(), "resultado2".to_string()])
}
}
/// Drop é chamado automaticamente quando Conexao sai de escopo
impl Drop for Conexao {
fn drop(&mut self) {
if self.ativa {
println!("[CONEXÃO] Fechando conexão com {}", self.endereco);
self.ativa = false;
}
}
}
fn main() {
{
let conn = Conexao::nova("localhost:5432");
let resultados = conn.executar_query("SELECT * FROM usuarios");
println!("Resultados: {:?}", resultados);
// conn sai de escopo aqui — Drop é chamado automaticamente!
}
println!("Após o escopo — conexão já foi fechada");
// Mesmo com retorno antecipado, Drop é chamado
fn funcao_com_erro() -> Result<(), String> {
let _conn = Conexao::nova("remoto:5432");
// Erro aqui — mas a conexão será fechada pelo Drop!
return Err("Algo deu errado".to_string());
// Este código nunca executa, mas não importa:
// Drop cuida da limpeza
}
match funcao_com_erro() {
Ok(_) => println!("Sucesso"),
Err(e) => println!("Erro: {} (mas a conexão foi fechada!)", e),
}
}
Guard Pattern
O Guard Pattern usa RAII para garantir que uma ação de cleanup ocorra, similar a defer em Go ou finally em Java:
use std::sync::{Mutex, Arc};
/// Guard para medir tempo de execução automaticamente
struct TimerGuard {
nome: String,
inicio: std::time::Instant,
}
impl TimerGuard {
fn novo(nome: &str) -> Self {
println!("[TIMER] Iniciando '{}'", nome);
TimerGuard {
nome: nome.to_string(),
inicio: std::time::Instant::now(),
}
}
}
impl Drop for TimerGuard {
fn drop(&mut self) {
let duracao = self.inicio.elapsed();
println!("[TIMER] '{}' levou {:?}", self.nome, duracao);
}
}
/// Guard para logging de entrada/saída de funções
struct LogGuard {
funcao: String,
}
impl LogGuard {
fn entrar(funcao: &str) -> Self {
println!("[LOG] >>> Entrando em {}", funcao);
LogGuard {
funcao: funcao.to_string(),
}
}
}
impl Drop for LogGuard {
fn drop(&mut self) {
println!("[LOG] <<< Saindo de {}", self.funcao);
}
}
/// Guard para backup temporário que restaura em caso de falha
struct BackupGuard<T: Clone> {
original: T,
referencia: *mut T,
comprometido: bool,
}
impl<T: Clone> BackupGuard<T> {
/// Cria um backup do valor atual
fn novo(valor: &mut T) -> Self {
BackupGuard {
original: valor.clone(),
referencia: valor as *mut T,
comprometido: false,
}
}
/// Confirma a mudança — o backup não será restaurado
fn comprometer(mut self) {
self.comprometido = true;
}
}
impl<T: Clone> Drop for BackupGuard<T> {
fn drop(&mut self) {
if !self.comprometido {
println!("[BACKUP] Restaurando valor original");
// Seguro porque mantemos a referência válida
unsafe {
*self.referencia = self.original.clone();
}
}
}
}
fn processar_com_guards() {
let _timer = TimerGuard::novo("processar_com_guards");
let _log = LogGuard::entrar("processar_com_guards");
println!(" Processando dados...");
std::thread::sleep(std::time::Duration::from_millis(100));
println!(" Processamento concluído");
// Ao sair da função, ambos os guards são dropados
// na ordem inversa de criação (LIFO)
}
fn main() {
processar_com_guards();
println!("---");
// Demonstrando MutexGuard (da biblioteca padrão)
let dados = Arc::new(Mutex::new(vec![1, 2, 3]));
{
// lock() retorna um MutexGuard — RAII para o lock
let mut guard = dados.lock().unwrap();
guard.push(4);
println!("Dados com lock: {:?}", *guard);
// MutexGuard é dropado aqui — lock é liberado automaticamente
}
// Lock já foi liberado — outra thread poderia adquirir agora
println!("Lock liberado. Dados: {:?}", dados.lock().unwrap());
}
Gerenciamento de Arquivos com RAII
use std::io::{self, Write, BufWriter};
use std::fs::File;
/// Arquivo temporário que se auto-deleta ao sair de escopo
struct ArquivoTemporario {
caminho: String,
arquivo: File,
}
impl ArquivoTemporario {
fn novo(prefixo: &str) -> io::Result<Self> {
let caminho = format!("/tmp/{}_{}.tmp", prefixo, std::process::id());
let arquivo = File::create(&caminho)?;
println!("[TEMP] Arquivo criado: {}", caminho);
Ok(ArquivoTemporario { caminho, arquivo })
}
fn escrever(&mut self, dados: &[u8]) -> io::Result<()> {
self.arquivo.write_all(dados)
}
/// Promove o arquivo temporário para permanente
/// Consome self sem chamar Drop — o arquivo NÃO é deletado
fn promover(self, novo_caminho: &str) -> io::Result<()> {
let caminho_antigo = self.caminho.clone();
// Importante: std::mem::forget previne Drop
std::mem::forget(self);
std::fs::rename(&caminho_antigo, novo_caminho)?;
println!("[TEMP] Promovido: {} -> {}", caminho_antigo, novo_caminho);
Ok(())
}
}
impl Drop for ArquivoTemporario {
fn drop(&mut self) {
println!("[TEMP] Deletando arquivo temporário: {}", self.caminho);
let _ = std::fs::remove_file(&self.caminho);
}
}
Diagrama
RAII: Ciclo de Vida do Recurso vinculado ao Escopo
fn exemplo() {
┌─── ESCOPO DA FUNÇÃO ──────────────────────┐
│ │
│ let conn = Conexao::nova("db"); │ ← recurso adquirido
│ │ │
│ │ { │
│ │ ┌─── ESCOPO INTERNO ────────────┐ │
│ │ │ let guard = mutex.lock(); │ │ ← lock adquirido
│ │ │ │ │ │
│ │ │ │ // usar guard... │ │
│ │ │ │ │ │
│ │ │ } ◄── Drop(guard) ───────────┘ │ ← lock liberado
│ │ │
│ │ // guard não existe mais aqui │
│ │ // mas conn ainda existe │
│ │ │
│ } ◄── Drop(conn) ────────────────────────┘ ← conexão fechada
│
│ // Nenhum recurso vazou!
Ordem de destruição — LIFO (pilha):
Criação: Destruição:
1. recurso_a 3. Drop(recurso_c) ← primeiro
2. recurso_b 2. Drop(recurso_b)
3. recurso_c 1. Drop(recurso_a) ← último
Guard Pattern — Cleanup automático:
┌───────────────────────┐
│ Guard │
│ ┌─────────────────┐ │
│ │ recurso: &mut R │ │ new() → adquire recurso
│ │ ativo: bool │ │
│ └─────────────────┘ │ drop() → libera recurso
│ │ (automaticamente!)
└───────────────────────┘
Exemplo do Mundo Real
Um guard de transação de banco de dados que faz rollback automaticamente se a transação não for comitada:
/// Simula uma conexão com banco de dados
struct BancoDeDados {
nome: String,
dados: std::collections::HashMap<String, String>,
}
impl BancoDeDados {
fn novo(nome: &str) -> Self {
println!("[BD] Banco '{}' inicializado", nome);
BancoDeDados {
nome: nome.to_string(),
dados: std::collections::HashMap::new(),
}
}
/// Inicia uma transação — retorna um guard RAII
fn iniciar_transacao(&mut self) -> Transacao<'_> {
println!("[BD] Iniciando transação em '{}'", self.nome);
Transacao {
banco: self,
operacoes: Vec::new(),
comitada: false,
}
}
}
/// Operação pendente na transação
#[derive(Debug, Clone)]
enum Operacao {
Inserir { chave: String, valor: String },
Atualizar { chave: String, valor_antigo: Option<String>, valor_novo: String },
Deletar { chave: String, valor_antigo: Option<String> },
}
/// Guard de transação — RAII garante rollback se não comitada
struct Transacao<'a> {
banco: &'a mut BancoDeDados,
operacoes: Vec<Operacao>,
comitada: bool,
}
impl<'a> Transacao<'a> {
/// Insere um registro na transação
fn inserir(&mut self, chave: &str, valor: &str) {
println!("[TX] INSERT {} = '{}'", chave, valor);
self.operacoes.push(Operacao::Inserir {
chave: chave.to_string(),
valor: valor.to_string(),
});
}
/// Atualiza um registro na transação
fn atualizar(&mut self, chave: &str, novo_valor: &str) {
let valor_antigo = self.banco.dados.get(chave).cloned();
println!("[TX] UPDATE {} = '{}' (antigo: {:?})", chave, novo_valor, valor_antigo);
self.operacoes.push(Operacao::Atualizar {
chave: chave.to_string(),
valor_antigo,
valor_novo: novo_valor.to_string(),
});
}
/// Deleta um registro na transação
fn deletar(&mut self, chave: &str) {
let valor_antigo = self.banco.dados.get(chave).cloned();
println!("[TX] DELETE {} (antigo: {:?})", chave, valor_antigo);
self.operacoes.push(Operacao::Deletar {
chave: chave.to_string(),
valor_antigo,
});
}
/// Confirma a transação — aplica todas as operações
fn comitar(mut self) -> Result<usize, String> {
let qtd = self.operacoes.len();
println!("[TX] COMMIT — aplicando {} operações", qtd);
for op in &self.operacoes {
match op {
Operacao::Inserir { chave, valor } => {
self.banco.dados.insert(chave.clone(), valor.clone());
}
Operacao::Atualizar { chave, valor_novo, .. } => {
self.banco.dados.insert(chave.clone(), valor_novo.clone());
}
Operacao::Deletar { chave, .. } => {
self.banco.dados.remove(chave);
}
}
}
self.comitada = true;
Ok(qtd)
}
}
/// Drop implementa ROLLBACK automático!
impl<'a> Drop for Transacao<'a> {
fn drop(&mut self) {
if !self.comitada && !self.operacoes.is_empty() {
println!(
"[TX] ROLLBACK automático! {} operações descartadas",
self.operacoes.len()
);
// As operações nunca foram aplicadas ao banco,
// então "rollback" é simplesmente não fazer nada.
// Em um sistema real, desfaria operações aplicadas.
}
}
}
fn main() {
let mut bd = BancoDeDados::novo("loja");
// Transação bem-sucedida
{
let mut tx = bd.iniciar_transacao();
tx.inserir("produto_1", "Teclado");
tx.inserir("produto_2", "Mouse");
tx.comitar().unwrap();
// comitada = true, Drop não faz rollback
}
println!("Dados após commit: {:?}\n", bd.dados);
// Transação que falha — ROLLBACK automático via Drop
{
let mut tx = bd.iniciar_transacao();
tx.inserir("produto_3", "Monitor");
tx.atualizar("produto_1", "Teclado Mecânico");
tx.deletar("produto_2");
// Simulando um erro: não chamamos comitar()
// Quando tx sai de escopo, Drop faz ROLLBACK!
println!("[SIMULAÇÃO] Erro detectado, saindo sem comitar...");
}
println!("Dados após rollback: {:?}\n", bd.dados);
// produto_3 NÃO foi inserido, produto_1 NÃO foi atualizado
// Transação com erro no meio
fn transacao_com_erro(bd: &mut BancoDeDados) -> Result<(), String> {
let mut tx = bd.iniciar_transacao();
tx.inserir("produto_4", "Webcam");
// Simulando erro
if true {
return Err("Erro de validação".to_string());
// tx é dropada pelo unwinding — ROLLBACK automático!
}
tx.comitar().map(|_| ())
}
match transacao_com_erro(&mut bd) {
Ok(_) => println!("Sucesso"),
Err(e) => println!("Erro: {} (rollback feito automaticamente)", e),
}
println!("Dados finais: {:?}", bd.dados);
}
Quando Usar
- Recursos que precisam de cleanup: Arquivos, conexões de rede, locks, memória mapeada
- Transações: Auto-rollback quando a transação não é confirmada
- Medição de tempo: Timer guards para profiling
- Logging estruturado: Log de entrada/saída de escopos
- Estado temporário: Configurações que devem ser restauradas ao sair do escopo
- Qualquer aquisição/liberação pareada: Sempre que
adquirir()precisa deliberar()
Quando NÃO Usar
- Recursos com vida longa: Se o recurso precisa sobreviver ao escopo, use
ArcouRc - Cleanup assíncrono:
Dropé síncrono; para cleanup async, useAsyncDrop(experimental) ou channels - Ordem de destruição crítica: Se a ordem de destruição entre campos de uma struct importa, cuidado — Rust destroi campos na ordem de declaração
- Efeitos colaterais indesejados: Se Drop tem efeitos colaterais (IO, rede), considere o impacto de drops em panic
Variações em Rust
Scoped Guard com closure
/// Guard genérico que executa uma closure no Drop
struct ScopeGuard<F: FnOnce()> {
callback: Option<F>,
}
impl<F: FnOnce()> ScopeGuard<F> {
fn novo(callback: F) -> Self {
ScopeGuard {
callback: Some(callback),
}
}
/// Desativa o guard — a closure NÃO será executada
fn desativar(mut self) {
self.callback = None;
}
}
impl<F: FnOnce()> Drop for ScopeGuard<F> {
fn drop(&mut self) {
if let Some(callback) = self.callback.take() {
callback();
}
}
}
/// Função auxiliar para criar scope guards facilmente
fn ao_sair<F: FnOnce()>(callback: F) -> ScopeGuard<F> {
ScopeGuard::novo(callback)
}
fn main() {
// Similar a defer em Go
let _guard = ao_sair(|| println!("Cleanup ao sair do escopo!"));
println!("Fazendo trabalho...");
println!("Mais trabalho...");
// "Cleanup ao sair do escopo!" será impresso aqui
}
ManuallyDrop para controle fino
use std::mem::ManuallyDrop;
/// Recurso que pode ser transferido sem Drop
struct RecursoTransferivel {
id: u64,
dados: String,
}
impl Drop for RecursoTransferivel {
fn drop(&mut self) {
println!("[DROP] Recurso {} destruído", self.id);
}
}
fn main() {
// ManuallyDrop previne Drop automático
let recurso = ManuallyDrop::new(RecursoTransferivel {
id: 1,
dados: "importante".to_string(),
});
println!("Recurso criado: id={}", recurso.id);
// Drop NÃO é chamado — o recurso deve ser liberado manualmente
// ou transferido para outro dono
}
Padrões Relacionados
- Builder: Builders podem usar RAII para garantir cleanup de estado parcial
- Type-State: Type-state com Drop para cleanup em transições de estado
- Newtype: Newtype com Drop para wrappers de recursos
- Command: Comandos de undo podem usar guards para rollback automático
Conclusão
RAII em Rust não é apenas um padrão — é o mecanismo fundamental de gerenciamento de recursos da linguagem. A trait Drop garante cleanup determinístico e automático, eliminando vazamentos de recursos, erros de double-free e use-after-free. O guard pattern estende RAII para cenários complexos como transações, medição de tempo e logging. Diferente de linguagens com garbage collector (onde finalizers são não-determinísticos) ou de C (onde cleanup manual é propenso a erros), Rust oferece o melhor dos dois mundos: controle total sobre o ciclo de vida dos recursos com segurança garantida pelo compilador.