Deref e Drop
Esta aula aborda as traits Deref e Drop do Rust, fundamentais para sobrecarregar o operador de desreferência (*) e gerenciar a limpeza de recursos. Aprenderemos sobre coerção de Deref, ordem de execução de Drop e a função std::mem::drop.
Nesta aula, exploraremos duas traits essenciais do Rust: Deref e Drop. A trait Deref permite que tipos implementem o comportamento do operador de desreferência (*), possibilitando que smart pointers e outros wrappers se comportem como referências. Já a trait Drop é executada automaticamente quando um valor sai de escopo, permitindo liberar recursos como memória, arquivos ou conexões de rede. Compreender essas traits é crucial para escrever código seguro e idiomático em Rust.
Além disso, veremos como a coerção de Deref simplifica o uso de referências e smart pointers, e como a ordem de execução de Drop pode afetar o design de estruturas aninhadas. Por fim, apresentaremos a função std::mem::drop, que força a liberação antecipada de um valor.
trait Deref e coerção
A trait Deref é definida na biblioteca padrão e permite que um tipo seja tratado como uma referência ao seu alvo. Sua assinatura é:
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}Quando você implementa Deref para um tipo, o operador * pode ser aplicado a ele, retornando uma referência ao alvo. Por exemplo, Box<T> implementa Deref para T, então *box equivale a *(box.deref()).
A coerção de Deref (deref coercion) é um mecanismo que converte automaticamente referências a um tipo que implementa Deref em referências ao seu Target. Isso ocorre em contextos como passagem de argumentos para funções, atribuições e chamadas de métodos. Por exemplo, uma função que espera &str pode receber &String porque String implementa Deref<Target=str>. Isso torna o código mais flexível e evita conversões explícitas.
Exemplo prático:
use std::ops::Deref;
struct MeuBox<T>(T);
impl<T> Deref for MeuBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn main() {
let x = MeuBox(42);
println!("{}", *x); // 42, graças a Deref
// coerção: &MeuBox<i32> vira &i32
fn pegar_ref(y: &i32) { println!("{}", y); }
pegar_ref(&x); // funciona
}trait Drop
A trait Drop permite personalizar o que acontece quando um valor sai de escopo. Ela possui apenas um método:
pub trait Drop {
fn drop(&mut self);
}O método drop é chamado automaticamente pelo compilador quando a variável que possui o valor deixa de ser válida (por exemplo, ao final de um bloco). É usado para liberar recursos como memória alocada no heap, descritores de arquivo ou conexões de rede. Importante: você não deve chamar drop diretamente (a menos que use std::mem::drop), pois isso causaria um double free.
Exemplo:
struct Custom {
data: String,
}
impl Drop for Custom {
fn drop(&mut self) {
println!("Liberando recurso: {}", self.data);
}
}
fn main() {
let c = Custom { data: String::from("arquivo.txt") };
println!("Dentro do escopo");
} // ao sair, drop é chamadoOrdem de drop
Em Rust, a ordem em que os drops ocorrem é determinada pela ordem de declaração das variáveis (primeiro a ser declarado, último a ser droppado) e, para structs, pela ordem dos campos (primeiro campo é droppado primeiro). Isso é importante quando há dependências entre recursos: por exemplo, se um campo guarda uma referência a outro campo, o campo referenciado deve ser droppado depois.
Considere:
struct Par {
a: String,
b: String,
}
impl Drop for Par {
fn drop(&mut self) {
println!("Droppando Par: a={}, b={}", self.a, self.b);
}
}
fn main() {
let p = Par { a: "primeiro".into(), b: "segundo".into() };
// ordem de drop: p.a, p.b, depois p (implícito)
// mas o drop de p é chamado após os campos
// Na verdade, o drop da struct é chamado antes dos campos?
// Regra: o drop da struct é executado, depois os campos são droppados na ordem de declaração.
// Então: drop de p (imprime), depois drop de p.a, depois drop de p.b
}Para evitar confusão, a documentação oficial recomenda que a implementação de Drop não dependa da ordem dos campos. Se precisar de ordem específica, considere usar std::mem::drop manualmente ou reestruturar o código.
std::mem::drop
A função std::mem::drop é uma forma de forçar a liberação de um valor antes do final do escopo. Ela simplesmente move o valor para dentro da função, que o descarta imediatamente, chamando seu drop. É útil para liberar recursos cedo, como fechar um arquivo antes do final da função.
Exemplo:
use std::mem::drop;
struct Recurso {
id: u32,
}
impl Drop for Recurso {
fn drop(&mut self) {
println!("Recurso {} liberado", self.id);
}
}
fn main() {
let r = Recurso { id: 1 };
drop(r); // libera aqui
println!("Após drop manual");
// r não pode mais ser usado
}Note que após drop(r), a variável r é movida e não pode mais ser acessada. Isso evita uso após liberação.
Referências
- Documentação oficial da trait Deref
- Documentação oficial da trait Drop
- Documentação de std::mem::drop
- The Rust Programming Language: Deref
- The Rust Programming Language: Drop
- Referência: Destrutores
Exercícios
Implemente a trait
Derefpara uma structWrapperque contém umi32. Faça com que*wrapperretorne o valor interno.✓ Resposta:use std::ops::Deref; struct Wrapper(i32); impl Deref for Wrapper { type Target = i32; fn deref(&self) -> &i32 { &self.0 } } fn main() { let w = Wrapper(10); assert_eq!(*w, 10); }Crie uma struct
Arquivoque implementeDrope imprima "Arquivo fechado" ao ser droppada. Instancie a struct e verifique a mensagem no final do escopo.✓ Resposta:struct Arquivo { nome: String, } impl Drop for Arquivo { fn drop(&mut self) { println!("Arquivo fechado: {}", self.nome); } } fn main() { let _f = Arquivo { nome: String::from("dados.txt") }; println!("Dentro do escopo"); } // Ao sair, "Arquivo fechado: dados.txt" é impressoExplique por que não se deve chamar
value.drop()diretamente. O que aconteceria?✓ Resposta:Chamardropdiretamente não é permitido pelo compilador, pois o métododropda traitDropé chamado automaticamente. Se você tentar chamarvalue.drop(), o compilador emitirá um erro. Para liberar manualmente, usestd::mem::drop(value), que move o valor e o descarta. Chamar o método diretamente causaria um conflito com o drop automático, resultando em double free (se fosse possível).Escreva um programa que demonstre a ordem de drop de uma struct com dois campos:
a: Stringeb: String. ImplementeDroppara a struct e para cada campo (crie tipos novos para os campos).✓ Resposta:struct CampoA(String); struct CampoB(String); impl Drop for CampoA { fn drop(&mut self) { println!("Droppando CampoA: {}", self.0); } } impl Drop for CampoB { fn drop(&mut self) { println!("Droppando CampoB: {}", self.0); } } struct MinhaStruct { a: CampoA, b: CampoB, } impl Drop for MinhaStruct { fn drop(&mut self) { println!("Droppando MinhaStruct"); } } fn main() { let s = MinhaStruct { a: CampoA("primeiro".into()), b: CampoB("segundo".into()), }; println!("Fim do escopo"); } // Ordem: drop de MinhaStruct, depois CampoA, depois CampoBUse
std::mem::droppara liberar um recurso antes do final de um bloco e mostre que a variável não pode mais ser usada.✓ Resposta:use std::mem::drop; struct Recurso { nome: String, } impl Drop for Recurso { fn drop(&mut self) { println!("Liberando {}", self.nome); } } fn main() { let r = Recurso { nome: String::from("conexão") }; drop(r); // println!("{}", r.nome); // Erro! r foi movido println!("Recurso já liberado"); }