Drop e liberação de recursos
1. Introdução ao trait Drop
O trait Drop é um dos mecanismos mais fundamentais do Rust para gerenciamento automático de recursos. Ele permite que você defina um comportamento personalizado que será executado quando um valor sair de escopo, garantindo que recursos como memória alocada, descritores de arquivo, conexões de rede ou bloqueios sejam liberados de forma segura e previsível.
A assinatura do trait é simples:
pub trait Drop {
fn drop(&mut self);
}
O compilador chama automaticamente o método drop quando uma variável atinge o fim de seu escopo léxico. Isso ocorre de forma determinística, diferentemente de linguagens com garbage collector, onde a liberação de recursos é imprevisível.
struct Recurso {
nome: String,
}
impl Drop for Recurso {
fn drop(&mut self) {
println!("Liberando recurso: {}", self.nome);
}
}
fn main() {
let r1 = Recurso { nome: "Arquivo.txt".to_string() };
let r2 = Recurso { nome: "Conexão BD".to_string() };
// r2 é dropado primeiro (ordem reversa de declaração)
// r1 é dropado em seguida
}
2. Implementando Drop para tipos customizados
Vamos implementar um gerenciador de recursos externos que simula uma conexão de banco de dados:
struct ConexaoBD {
id: u32,
ativa: bool,
}
impl ConexaoBD {
fn nova(id: u32) -> Self {
println!("Criando conexão BD #{}", id);
ConexaoBD { id, ativa: true }
}
fn executar_query(&self, query: &str) {
if self.ativa {
println!("Conexão #{} executando: {}", self.id, query);
}
}
}
impl Drop for ConexaoBD {
fn drop(&mut self) {
println!("Fechando conexão BD #{}", self.id);
self.ativa = false;
// Aqui liberaríamos recursos reais do sistema
}
}
fn main() {
let conn = ConexaoBD::nova(42);
conn.executar_query("SELECT * FROM usuarios");
// conn.drop() é chamado automaticamente aqui
}
Boas práticas importantes:
- Nunca use panic! dentro de drop, pois isso pode causar abort durante unwind
- Mantenha drop o mais leve possível
- Não confie em estado global dentro de drop
3. A ordem de liberação de recursos
A ordem de destruição segue regras precisas:
struct RecursoA;
struct RecursoB;
impl Drop for RecursoA {
fn drop(&mut self) { println!("Drop A"); }
}
impl Drop for RecursoB {
fn drop(&mut self) { println!("Drop B"); }
}
struct Container {
a: RecursoA,
b: RecursoB,
}
impl Drop for Container {
fn drop(&mut self) { println!("Drop Container"); }
}
fn main() {
let x = RecursoA;
let y = RecursoB;
// y é dropado primeiro (declarado depois)
// x é dropado em seguida
let c = Container { a: RecursoA, b: RecursoB };
// c é dropado, depois b, depois a (campos em ordem reversa)
}
Para variáveis temporárias e closures:
fn main() {
let recurso = RecursoA;
let closure = || {
let temp = RecursoB;
// temp é dropado quando a closure termina
println!("Executando closure");
};
closure();
// recurso é dropado aqui
}
4. Drop e ownership: a regra do "mover"
O drop interage diretamente com a semântica de movimento. Quando um valor é movido, a responsabilidade pelo drop passa para o novo proprietário:
struct Dados {
valores: Vec<i32>,
}
impl Drop for Dados {
fn drop(&mut self) {
println!("Dropando {} valores", self.valores.len());
}
}
fn processar(dados: Dados) {
// dados é movido para cá
println!("Processando...");
} // dados.drop() é chamado aqui
fn main() {
let d = Dados { valores: vec![1, 2, 3] };
processar(d); // d é movido
// println!("{:?}", d.valores); // ERRO! d não é mais dono
}
Em coleções, o drop é recursivo:
fn main() {
let mut mapa = std::collections::HashMap::new();
mapa.insert("chave1", Dados { valores: vec![1] });
mapa.insert("chave2", Dados { valores: vec![2, 3] });
// Quando mapa sai de escopo, cada Dados dentro dele é dropado
}
5. Drop e referências: cuidado com borrowing
Uma limitação importante: você não pode implementar Drop em tipos que contêm referências sem lifetimes específicos:
// struct ComReferencia<'a> {
// valor: &'a str,
// }
//
// impl Drop for ComReferencia<'_> {
// fn drop(&mut self) {
// // Isso não compila! O compilador não permite
// }
// }
Para contornar, use std::mem::drop para liberar referências manualmente:
fn main() {
let dados = String::from("importante");
let referencia = &dados;
// std::mem::drop(referencia); // ERRO! não pode dropar referência
// Em vez disso, deixe a referência sair de escopo naturalmente
{
let r = &dados;
println!("{}", r);
} // r sai de escopo, dados continua vivo
std::mem::drop(dados); // Isso funciona! Drop manual do proprietário
}
6. std::mem::drop vs. Drop::drop
Nunca chame Drop::drop diretamente — isso causaria dupla liberação:
struct Explosivo;
impl Drop for Explosivo {
fn drop(&mut self) {
println!("💥 BOOM!");
}
}
fn main() {
let mut bomba = Explosivo;
// bomba.drop(); // ERRO DE COMPILAÇÃO! Não pode chamar explicitamente
// Use std::mem::drop para liberar antecipadamente
std::mem::drop(bomba);
// println!("Ainda existe?"); // ERRO! bomba foi movida para drop
// Para evitar drop completamente:
let outra = Explosivo;
std::mem::forget(outra); // drop NUNCA será chamado
// Ou use ManuallyDrop para controle fino:
use std::mem::ManuallyDrop;
let mut controlado = ManuallyDrop::new(Explosivo);
// Podemos acessar o valor interno, mas drop não é automático
}
7. Drop e RAII (Resource Acquisition Is Initialization)
Rust implementa RAII perfeitamente através do trait Drop. Exemplos clássicos na biblioteca padrão:
use std::fs::File;
use std::io::Write;
use std::sync::Mutex;
fn main() {
// File implementa Drop para fechar o descritor
let mut arquivo = File::create("exemplo.txt").unwrap();
writeln!(arquivo, "Dados importantes").unwrap();
// arquivo é fechado automaticamente aqui
// MutexGuard implementa Drop para liberar o lock
let mutex = Mutex::new(42);
{
let guard = mutex.lock().unwrap();
println!("Valor: {}", *guard);
} // guard.drop() libera o lock automaticamente
// Box<T> implementa Drop para liberar memória heap
let ponteiro = Box::new(100);
println!("Heap: {}", ponteiro);
// memória é liberada automaticamente
}
Comparado com C++, Rust oferece garantias mais fortes: drop é sempre chamado (a menos que você use forget explicitamente), e não há exceções não tratadas que possam pular a liberação.
8. Drop em cenários avançados
Drop com Arc e Rc:
use std::rc::Rc;
use std::sync::Arc;
struct RecursoContado {
id: u32,
}
impl Drop for RecursoContado {
fn drop(&mut self) {
println!("Recurso #{} liberado", self.id);
}
}
fn main() {
let rc = Rc::new(RecursoContado { id: 1 });
let rc2 = Rc::clone(&rc);
println!("Contagem: {}", Rc::strong_count(&rc)); // 2
std::mem::drop(rc); // drop não é chamado ainda (contagem = 1)
println!("Contagem: {}", Rc::strong_count(&rc2)); // 1
// drop real ocorre quando rc2 sai de escopo (contagem = 0)
}
Interação com RefCell:
use std::cell::RefCell;
struct ComDrop {
dados: RefCell<String>,
}
impl Drop for ComDrop {
fn drop(&mut self) {
// Cuidado! Não podemos ter borrows ativos durante drop
let mut dados = self.dados.borrow_mut();
dados.push_str(" (liberado)");
println!("Drop: {}", dados);
}
}
Estruturas cíclicas e Weak:
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct No {
valor: i32,
proximo: RefCell<Option<Rc<No>>>,
}
impl Drop for No {
fn drop(&mut self) {
println!("Drop nó {}", self.valor);
}
}
fn main() {
let a = Rc::new(No { valor: 1, proximo: RefCell::new(None) });
let b = Rc::new(No { valor: 2, proximo: RefCell::new(None) });
// Cria referência cíclica: a -> b -> a
*a.proximo.borrow_mut() = Some(Rc::clone(&b));
*b.proximo.borrow_mut() = Some(Rc::clone(&a));
// Vazamento de memória! Nenhum nó será dropado
// Solução: usar Weak para uma das referências
}
O trait Drop é a espinha dorsal do gerenciamento de recursos em Rust. Dominá-lo é essencial para escrever código seguro e eficiente, aproveitando ao máximo as garantias de memória que a linguagem oferece.
Referências
- The Rust Programming Language - Drop — Capítulo oficial do livro sobre o trait Drop com exemplos práticos
- Rust Reference - Drop — Documentação detalhada sobre destrutores e ordem de drop na referência da linguagem
- Rust by Example - Drop — Tutorial interativo com exemplos de implementação do trait Drop
- std::ops::Drop Documentation — Documentação oficial da API do trait Drop na biblioteca padrão
- RAII in Rust — Explicação do padrão RAII em Rust com exemplos de gerenciamento de arquivos e mutexes
- The Drop Check Rule — Artigo avançado do Rustonomicon sobre as regras de verificação de drop e lifetimes