Historia de la cuestión
En Rust, los tipos a menudo encapsulan otros valores (por ejemplo, Box, Rc), y a los desarrolladores les gustaría tener acceso transparente a los valores anidados, como con los punteros en C++. Se agregó el trait Deref (y DerefMut) y una lógica especial de auto-deref para facilitar el acceso a través de métodos y operadores.
Problema
La confusión sobre cuándo y cómo se desreferencian automáticamente los punteros y punteros inteligentes puede llevar a errores de compilación, comportamientos inesperados de los métodos, y a veces incluso a código no óptimo debido a copias o préstamos innecesarios.
Solución
El trait Deref permite describir la propia regla de desreferenciación (por ejemplo, Box<T> se comporta como T gracias a la implementación de Deref). El sistema de auto-deref intenta "desreferenciar" referencias a tipos Deref al acceder a métodos y operadores. Esto permite escribir:
use std::rc::Rc; fn print_len(s: &str) { println!("{}", s.len()); } let s: Rc<String> = Rc::new("hello".into()); print_len(&s); // auto-deref: &Rc<String> -> &String -> &str
Características clave:
*, así como al hacer coincidir tipos de referencia.¿Ocurre siempre la auto-deref cuando lo "espera" el desarrollador?
No: la coerción deref ocurre solo al llamar a métodos o cuando se requiere una referencia a otro tipo especificado en la firma a través de Deref Target. No funciona en todas las expresiones, por ejemplo, en coincidir patrones.
¿Puede la coerción deref romper las reglas de préstamo y posesión?
No. La coerción Deref cumple completamente con las reglas de préstamo: no es posible obtener dos referencias mutables a través de Deref, violando la seguridad de posesión de Rust.
¿Es posible la auto-deref de &T a &U, si T: Deref<Target=U> y ambos tipos no están relacionados explícitamente?
Sí, al llamar a una función que espera &U, pasar &T, donde T implementa Deref<Target=U>, dará lugar a la conversión automática.
Ejemplo de código:
struct Wrapper(String); impl std::ops::Deref for Wrapper { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } fn takes_str(s: &str) {} let w = Wrapper("mytext".into()); takes_str(&w); // auto-deref: &Wrapper -> &String -> &str
El desarrollador implementó su puntero inteligente, pero no añadió la implementación de Deref, esperando que su tipo se comportara como un valor normal.
Ventajas:
El tipo se compila y se puede acceder explícitamente al valor anidado.
Desventajas:
La auto-deref deja de funcionar al llamar a métodos, lo que resulta incómodo al usar funciones de la stdlib y bibliotecas de terceros.
Su estructura envolvente implementa Deref y DerefMut de manera similar a Box, integración sin problemas con todas las funciones estándar.
Ventajas:
Comodidad, operación transparente de la mayoría de las funciones del lenguaje, limpieza y suave integración con el resto del código.
Desventajas:
Riesgo de complicar el interfaz de Deref si el tipo objetivo no es obvio.