Historique de la question
En Rust, les types encapsulent souvent d'autres valeurs (par exemple, Box, Rc), et les développeurs souhaitaient un accès transparent aux valeurs imbriquées, comme avec les pointeurs en C++. Le trait Deref (et DerefMut) a été ajouté ainsi qu'une logique spéciale de déferrement automatique pour un accès pratique via des méthodes et des opérateurs.
Problème
La confusion sur quand et comment les pointeurs et les pointeurs intelligents sont automatiquement déferrés conduit à des erreurs de compilation, à des comportements inattendus des méthodes, et parfois même à un code sous-optimal en raison de copies ou de emprunts superflus.
Solution
Le trait Deref permet de définir sa propre règle de déferrement (par exemple, Box<T> se comporte comme T grâce à l'implémentation de Deref). Le système auto-deref tente de "déferer" les références aux types Deref lors de l’accès aux méthodes et aux opérateurs. Cela permet d'écrire :
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
Caractéristiques clés :
*, ainsi que lors de la correspondance des types de références.Le déferrement automatique se produit-il toujours quand le développeur "s'attend" à cela ?
Non : la coercion deref se produit uniquement lors de l'appel de méthodes ou lorsqu'une référence à un autre type, spécifiée dans la signature via Deref Target, est nécessaire. Elle ne fonctionne pas dans toutes les expressions, par exemple lors du pattern matching.
La coercion deref peut-elle casser les règles d'emprunt et de propriété ?
Non. La coercion deref respecte entièrement les règles d'emprunt — il est impossible d'obtenir deux références mutables via Deref, violant ainsi la sécurité de propriété de Rust.
Un déferrement automatique est-il possible de &T à &U, si T: Deref<Target=U> et les deux types ne sont pas explicitement liés ?
Oui, lors de l'appel d'une fonction s'attendant à &U, passer &T, où T implémente Deref<Target=U>, entraînera une conversion automatique.
Exemple de code :
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
Un développeur a implémenté son propre pointeur intelligent, mais n'a pas ajouté l'implémentation de Deref, s’attendant à ce que son type se comporte comme une valeur ordinaire.
Avantages :
Le type compile et il est possible d'accéder explicitement à la valeur imbriquée.
Inconvénients :
L'auto-deref cesse de fonctionner lors de l'appel de méthodes, rendant l'utilisation des fonctions de la stdlib et des bibliothèques tierces moins pratique.
Sa structure d'enveloppement implémente Deref et DerefMut de manière similaire à Box, intégration transparente avec toutes les fonctions standard.
Avantages :
Convivialité, fonctionnement transparent de la plupart des fonctions du langage, clarté et intégration douce avec le reste du code.
Inconvénients :
Risque de compliquer l'interface Deref si le type cible n'est pas évident.