🚧 Attention, peinture fraîche !
Cette page a été traduite par une seule personne et n'a pas été relue et vérifiée par quelqu'un d'autre ! Les informations peuvent par exemple être erronées, être formulées maladroitement, ou contenir d'autres types de fautes.
Vous pouvez contribuer à l'amélioration de cette page sur sa Pull Request.
Annexe C : les traits dérivables
Dans de nombreux endroits du livre, nous avons vu l'attribut derive, que vous
pouvez appliquer à une définition de structure ou d'énumération. L'attribut
derive génère du code qui va implémenter un trait avec sa propre
implémentation par défaut sur le type que vous avez annoté avec la syntaxe
derive.
Dans cette annexe, nous allons produire une référence de tous les traits de la
bibliothèque standard que vous pouvez utiliser avec derive. Chaque section
va donner :
- Quels opérateurs et méthodes seront activés en dérivant de ce trait
- Ce que fait l'implémentation du trait appliqué par le
derive - Ce que l'implémentation du trait implique sur le type concerné
- Les conditions dans lesquelles vous pouvez ou non implémenter le trait
- Des exemples d'opérations qui nécessitent que le trait soit implémenté
Si vous souhaitez appliquer un comportement différent de celui fourni par
l'attribut derive, consultez la documentation de la bibliothèque
standard pour le trait
concerné afin d'en savoir plus sur son implémentation manuelle.
Le reste des traits définis dans la bibliothèque standard ne peuvent pas être
implémentés sur des types en utilisant derive. Ces traits n'ont pas de
comportement logique par défaut, donc c'est à vous de les implémenter de la
façon la plus appropriée pour ce que vous souhaitez accomplir.
Un exemple de trait qui ne peut pas être dérivé est Display, qui permet de
formater la donnée pour les utilisateurs finaux. Vous devez toujours réfléchir
au formatage du type le plus approprié pour un utilisateur final. Quelles
parties d'un type un utilisateur final devrait pouvoir voir ? Sous quelle forme
les données devraient être les plus intéressantes pour eux ? Le compilateur de
Rust n'a pas cette intuition, donc il ne peut pas fournir un comportement par
défaut à votre place.
La liste des traits dérivables fournis dans cette annexe n'est pas exhaustive :
les bibliothèques peuvent implémenter derive pour leurs propres traits,
étendant potentiellement à l'infini la liste de traits que vous pouvez utiliser
avec derive. L'implémentation de derive implique l'utilisation d'une macro
procédurale, que nous avons vu dans une section du
chapitre 19.
Debug pour l'affichage au développeur
Le trait Debug permet le formatage de déboguage pour mettre en forme en tant
que chaînes de caractères, que vous pouvez utiliser en ajoutant :? dans un
espace réservé {}.
Le trait Debug vous permet d'afficher des instances d'un type pour des besoins
de déboguage, afin que vous et les autres développeurs qui utilisent votre type
puissent inspecter une de ses instances à un endroit précis de l'exécution du
programme.
Le trait Debug est nécessaire, par exemple, pour l'utilisation de la macro
assert_eq!. Cette macro affiche les valeurs des instances passées en argument
dans le cas où l'affirmation échoue afin que le développeur puisse voir pourquoi
les deux instances ne sont pas égales.
PartialEq et Eq pour comparer l'égalité
Le trait PartialEq vous permet de comparer des instances d'un type pour
vérifier leur égalité et permet l'utilisation des opérateurs == et !=.
L'application de derive avec PartialEq implémente la méthode eq. Lorsque
PartialEq est dérivé sur une structure, deux instances ne peuvent être égales
seulement si tous leurs champs sont égaux, et les instances ne sont pas égales
si un des champs n'est pas égal. Lorsque ce trait est dérivé sur une
énumération, chaque variante est égale à elle-même et n'est pas égale aux autres
variantes.
Le trait Eq est nécessaire, par exemple, pour utiliser la macro assert_eq!,
qui nécessite de pouvoir comparer l'égalité de deux instances d'un type.
Le trait Eq n'a pas de méthode. Son rôle est de signaler que pour chaque
valeur du type annoté, la valeur est égale à elle-même. Le trait Eq peut
seulement être appliqué sur des types qui implémentent PartialEq, bien que
tous les types qui implémentent PartialEq ne puissent pas implémenter Eq. Un
exemple de ceci sont les types de nombres à virgule flottante : l'implémentation
des nombres Ă virgule flottante stipule que deux instances ayant la valeur
“not-a-number” (NaN, c'est-à -dire “ceci n'est pas un nombre”) ne sont pas
égales entre elles.
Par exemple, Eq est nécessaire est pour les clés dans un HashMap<K, V> afin
que le HashMap<K, V> puisse déterminer si deux clés sont identiques.
PartialOrd et Ord pour comparer les ordres de grandeur
Le trait PartialOrd vous permet de comparer des instances d'un type pour
pouvoir les trier. Un type qui implémente PartialOrd peut être utilisé avec
les opérateurs <, >, <=, et >=. Vous pouvez appliquer uniquement le
trait PartialOrd aux types qui implémentent aussi PartialEq.
L'application de derive avec PartialOrd implémente la méthode partial_cmp,
qui retourne un Option<Ordering> qui vaudra None lorsque les valeurs
fournies ne fournissent pas un ordre. Un exemple de valeur qui ne produit pas
d'ordre, même si la plupart des valeurs de ce type peuvent être comparées, est
la valeur “not-a-number” (NaN) des virgules flottantes. L'appel Ă
partial_cmp entre n'importe quel nombre Ă virgule flottante et la valeur NaN
de virgule flottante va retourner None.
Lorsqu'il est dérivé sur une structure, PartialOrd compare deux instances en
comparant les valeurs de chaque champ dans l'ordre dans lequel les champs
apparaissent dans la définition de la structure. Lorsqu'il est dérivé sur des
énumérations, les variantes de l'énumération déclarées plus tôt dans la
définition de l'énumération sont considérées inférieures aux variantes déclarées
ensuite.
Le trait PartialOrd est nécessaire, par exemple, pour la méthode gen_range
de la crate rand qui génère une valeur aléatoire dans l'intervalle contrainte
par une valeur minimale et une valeur maximale.
Le trait Ord vous permet de savoir si un ordre valide existe toujours entre
deux valeurs du type annoté. Le trait Ord implémente la méthode cmp, qui
retourne un Ordering plutĂ´t qu'une Option<Ordering> car un ordre valide sera
toujours possible. Vous pouvez appliquer le trait Ord uniquement sur les types
qui implémentent aussi PartialOrd et Eq (et Eq nécessite PartialEq).
Lorsqu'il est dérivé sur des structures et des énumérations, cmp se comporte
de la même manière que l'implémentation de partial_cmp dérivée de
PartialOrd.
Par exemple, Ord doit être implémenté sur le type de valeurs que nous stockons
dans un BTreeSet<T>, qui est une structure de donnée qui stocke des données en
fonction de l'ordre de tri de ces valeurs.
Clone et Copy pour dupliquer des valeurs
Le trait Clone vous permet de créer explicitement une copie profonde d'une
valeur, et le processus de duplication peut impliquer l'exécution d'un code
arbitraire pour copier les données stockées dans le tas. Rendez-vous à la
section “Les interactions entre les variables et les données : le
déplacement” du
chapitre 4 pour plus d'informations sur Clone.
Utiliser derive avec Clone implémente la méthode clone, qui, lorsqu'elle
est implémentée sur tout le type, fait appel à clone sur chaque constituant du
type. Cela signifie que tous les champs ou les valeurs dans le type doivent
aussi implémenter Clone pour dériver de Clone.
Clone est par exemple nécessaire lorsque nous appelons la méthode to_vec sur
une slice. La slice ne prend pas possession des instances du type qu'il
contient, mais le vecteur retourné par to_vec va avoir besoin de prendre
possession de ses instances, donc to_vec fait appel Ă clone sur chaque
élément. C'est pourquoi le type stocké dans la slice doit implémenter Clone.
Le trait Copy vous permet de dupliquer une valeur en copiant uniquement les
éléments stockés sur la pile ; il n'est pas nécessaire d'avoir de code
arbitraire. Rendez-vous à la section “Données uniquement sur la pile : la
copie” du chapitre 4 pour plus
d'informations sur Copy.
Le trait Copy ne définit pas de méthode, volontairement pour empêcher les
développeurs de surcharger ces méthodes et ainsi violer l'affirmation qu'aucun
code arbitraire est exécuté à la copie. Ainsi, tous les développeurs peuvent
compter sur le fait qu'une copie de valeur est très rapide.
Vous pouvez utiliser derive avec Copy sur n'importe quel type constitué
d'éléments qui implémentent aussi Copy. Vous ne pouvez appliquer le trait
Copy que sur des types qui implémentent aussi Clone, car un type qui
implémente Copy a aussi une implémentation triviale de Clone qui procède
aux mĂŞmes actions que Copy.
Le trait Copy est rarement nécessaire ; les types qui implémentent Copy
peuvent être optimisés, ce qui veut dire que vous n'avez pas à appeler clone,
ce qui rend le code plus concis.
Tout ce que vous pouvez accomplir avec Copy, vous pouvez le faire avec
Clone, mais le code risque d'ĂŞtre plus lent ou doit parfois utiliser clone.
Hash pour faire correspondre une valeur avec une valeur de taille fixe
Le trait Hash vous permet d'obtenir une valeur Ă taille fixe en utilisant une
fonction de hachage sur une instance d'un type d'une taille quelconque. Utiliser
derive avec Hash implémente la méthode hash. L'implémentation dérive de la
méthode hash combine le résultat de l'appel de hash sur chaque élément du
type, ce qui signifie que tous ses champs ou valeurs doivent aussi implémenter
Hash pour pouvoir lui appliquer le trait Hash.
Pour stocker des clés efficacement dans un HashMap<K, V>, les clés doivent
nécessairement implémenter Hash.
Default pour des valeurs par défaut
Le trait Default vous permet de créer une valeur par défaut pour un type.
Implémenter Default avec derive ajoute la fonction default. Cette fonction
default fait elle-même appel à la fonction default sur chaque élément du
type, ce qui signifie que tous les champs ou les valeurs dans le type doit aussi
implémenter Default pour que ce type puisse dériver de Default.
La fonction Default::default est couramment utilisé en association avec la
syntaxe de modification de structures que nous avons vu dans la section “Créer
des instances Ă partir d'autres instances avec la syntaxe de mise Ă jour de
structure”
du chapitre 5. Vous pouvez personnaliser quelques champs d'une structure et
ensuite définir et utiliser une valeur par défaut pour le reste des champs en
utilisant ..Default::default().
Le trait Default est nécessaire lorsque vous utilisez la méthode
unwrap_or_default sur les instances de Option<T>, par exemple. Si le
Option<T> vaut None, la méthode unwrap_or_default va retourner le résultat
de Default::default sur le type T provenant du Option<T>.