🚧 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>
.