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