Deref y Drop son traits para desreferenciar y limpiar recursos en tipos como los punteros inteligentes.
Un Smart Pointer suele ser un Struct.
Box<T>es un struct.Stringes un struct.Rc<T>es un struct.
Normalmente, no puedes desreferenciar un struct (*mi_struct daría error). Todos los valores se destruyen al salir de su ámbito y sus campos también se destruyen; para añadir una acción de limpieza propia implementamos Drop.
El trait Deref
El trait Deref permite personalizar el comportamiento del operador de desreferencia *.
Sin Deref, si creamos nuestro propio puntero inteligente, tendríamos que acceder al valor interno manualmente cada vez.
Implementando nuestro propio Box
Vamos a crear un struct tonto llamado MiCaja para ver el problema.
struct MiCaja<T>(T); // Tuple struct genérico
impl<T> MiCaja<T> {
fn new(x: T) -> MiCaja<T> {
MiCaja(x)
}
}
fn main() {
let x = 5;
let y = MiCaja::new(x);
assert_eq!(5, x);
// assert_eq!(5, *y); // ❌ Error: type `MiCaja<{integer}>` cannot be dereferenced
}Rust no sabe cómo aplicar * a MiCaja. Para arreglarlo, implementamos Deref:
use std::ops::Deref;
impl<T> Deref for MiCaja<T> {
type Target = T; // Definimos qué tipo hay dentro
fn deref(&self) -> &Self::Target {
&self.0 // Devolvemos una referencia al primer elemento de la tupla
}
}Ahora, cuando escribimos *y, Rust en realidad ejecuta tras bambalinas:
*(y.deref())
Obtiene la referencia interna y luego la desreferencia. ¡Ahora nuestro struct se comporta como un puntero!
Coerción de Deref
Gracias a Deref, Rust ofrece una característica de calidad de vida increíble llamada Deref Coercion.
Rust puede convertir automáticamente una referencia a un puntero inteligente en una referencia a su contenido. Y lo hace en cadena.
Imagina una función que espera un slice de string (&str):
fn saludar(nombre: &str) {
println!("Hola, {}!", nombre);
}
fn main() {
let m = MiCaja::new(String::from("Rust"));
// Llamamos a la función pasando &MiCaja<String>
saludar(&m); // ✅ ¡Funciona!
}¿Qué ha pasado aquí?
- Rust ve que
saludarquiere&str. - Le pasamos
&MiCaja. Llama aderef-> obtenemos&String. Stringtambién implementaDeref(haciastr). Llama aderef-> obtenemos&str.- ¡Coincide!
Sin esta característica, tendríamos que haber escrito: saludar(&(*m)[..]). Gracias Deref.
El trait Drop
El segundo pilar es Drop. Este trait nos permite ejecutar código cuando un valor va a salir del ámbito (scope). Es el equivalente a un Destructor en C++.
Es importante para los Smart Pointers:
BoxusaDroppara liberar la memoria del Heap.RcusaDroppara restar 1 al contador de referencias.FileusaDroppara cerrar el archivo.MutexGuardusaDroppara liberar el bloqueo.
Implementando Drop
Vamos a crear un struct ruidoso que nos avise cuando muere.
struct PunteroRuidoso {
dato: String,
}
impl Drop for PunteroRuidoso {
fn drop(&mut self) {
println!("Limpiando puntero con dato: `{}`", self.dato);
}
}
fn main() {
let c = PunteroRuidoso { dato: String::from("C") };
let d = PunteroRuidoso { dato: String::from("D") };
println!("Punteros creados.");
} // Aquí termina el scopeSalida:
Punteros creados.
Limpiando puntero con dato: `D`
Limpiando puntero con dato: `C`Fíjate en el orden: las variables locales se destruyen en orden inverso a su declaración. Por eso d se destruye antes que c, independientemente de cómo decida el compilador almacenar físicamente esos valores.
Forzando la limpieza (std::mem::drop)
A veces quieres liberar algo antes de que termine el ámbito (por ejemplo, para liberar un Lock o cerrar un archivo para que otro proceso pueda usarlo).
No puedes llamar al método drop manualmente:
// c.drop(); // ❌ Error explícito: Destructor explícito no permitidoRust prohíbe esto para evitar el error de “Double Free” (intentar liberar memoria dos veces: una manual y otra automática al final del scope).
Si quieres forzarlo, usa la función de la librería estándar:
fn main() {
let c = PunteroRuidoso { dato: String::from("C") };
println!("Antes del drop");
std::mem::drop(c); // Forzamos la liberación aquí
println!("Después del drop");
}