Annexe C : Les traits dérivables
À plusieurs endroits dans ce livre, nous avons abordé 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 implémente 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 fournissons une référence de tous les traits de la bibliothèque standard que vous pouvez utiliser avec derive. Chaque section couvre :
- Quels opérateurs et méthodes la dérivation de ce trait activera
- Ce que fait l’implémentation du trait fournie par
derive - Ce que l’implémentation du trait signifie pour le type
- Les conditions dans lesquelles vous êtes autorisé ou non à implémenter le trait
- Des exemples d’opérations qui nécessitent le trait
Si vous souhaitez un comportement différent de celui fourni par l’attribut derive, consultez la documentation de la bibliothèque standard pour chaque trait afin d’obtenir des détails sur la manière de les implémenter manuellement.
Les traits listés ici sont les seuls définis par la bibliothèque standard qui peuvent être implémentés sur vos types en utilisant derive. Les autres traits définis dans la bibliothèque standard n’ont pas de comportement par défaut pertinent, c’est donc à vous de les implémenter de la manière qui correspond à ce que vous essayez d’accomplir.
Un exemple de trait qui ne peut pas être dérivé est Display, qui gère le formatage pour les utilisateurs finaux. Vous devriez toujours réfléchir à la manière appropriée d’afficher un type pour un utilisateur final. Quelles parties du type un utilisateur final devrait-il pouvoir voir ? Quelles parties trouverait-il pertinentes ? Quel format des données serait le plus pertinent pour lui ? Le compilateur Rust n’a pas cette connaissance, il ne peut donc pas fournir un comportement par défaut approprié pour vous.
La liste des traits dérivables fournie dans cette annexe n’est pas exhaustive : les bibliothèques peuvent implémenter derive pour leurs propres traits, ce qui rend la liste des traits utilisables avec derive véritablement ouverte. L’implémentation de derive implique l’utilisation d’une macro procédurale, qui est traitée dans la section « Les macros derive personnalisées » du chapitre 20.
Debug pour l’affichage destiné aux développeurs
Le trait Debug activé le formatage de débogage dans les chaînes de formatage, que vous indiquez en ajoutant :? à l’intérieur des espaces réservés {}.
Le trait Debug vous permet d’afficher des instances d’un type à des fins de débogage, afin que vous et les autres développeurs utilisant votre type puissiez inspecter une instance à un moment précis de l’exécution d’un programme.
Le trait Debug est requis, par exemple, lors de l’utilisation de la macro assert_eq!. Cette macro affiche les valeurs des instances passées en arguments si l’assertion d’égalité échoue, afin que les développeurs puissent voir pourquoi les deux instances n’étaient pas égales.
PartialEq et Eq pour les comparaisons d’égalité
Le trait PartialEq vous permet de comparer des instances d’un type pour vérifier l’égalité et activé l’utilisation des opérateurs == et !=.
Dériver PartialEq implémente la méthode eq. Lorsque PartialEq est dérivé sur des structures, deux instances sont égales uniquement si tous les champs sont égaux, et les instances ne sont pas égales si un quelconque champ n’est pas égal. Lorsqu’il est dérivé sur des énumérations, chaque variante est égale à elle-même et différente des autres variantes.
Le trait PartialEq est requis, par exemple, lors de l’utilisation de la macro assert_eq!, qui doit pouvoir comparer deux instances d’un type pour vérifier l’égalité.
Le trait Eq n’a pas de méthodes. Son objectif est de signaler que pour chaque valeur du type annoté, la valeur est égale à elle-même. Le trait Eq ne peut être appliqué qu’aux types qui implémentent également PartialEq, bien que tous les types qui implémentent PartialEq ne puissent pas implémenter Eq. Un exemple est celui des types de nombres à virgule flottante : l’implémentation des nombres à virgule flottante stipule que deux instances de la valeur « pas un nombre » (NaN) ne sont pas égales entre elles.
Un exemple où Eq est requis est pour les clés d’un HashMap<K, V>, afin que le HashMap<K, V> puisse déterminer si deux clés sont identiques.
PartialOrd et Ord pour les comparaisons d’ordre
Le trait PartialOrd vous permet de comparer des instances d’un type à des fins de tri. Un type qui implémente PartialOrd peut être utilisé avec les opérateurs <, >, <= et >=. Vous ne pouvez appliquer le trait PartialOrd qu’aux types qui implémentent également PartialEq.
Dériver PartialOrd implémente la méthode partial_cmp, qui retourné un Option<Ordering> qui sera None lorsque les valeurs fournies ne produisent pas d’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 à virgule flottante NaN. Appeler partial_cmp avec n’importe quel nombre à virgule flottante et la valeur à virgule flottante NaN retournera None.
Lorsqu’il est dérivé sur des structures, PartialOrd compare deux instances en comparant la valeur 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 sont considérées comme inférieures aux variantes listées après.
Le trait PartialOrd est requis, par exemple, pour la méthode gen_range du crate rand qui génère une valeur aléatoire dans l’intervalle spécifié par une expression d’intervalle.
Le trait Ord vous permet de savoir que pour deux valeurs quelconques du type annoté, un ordre valide existera. Le trait Ord implémente la méthode cmp, qui retourné un Ordering plutôt qu’un Option<Ordering> car un ordre valide sera toujours possible. Vous ne pouvez appliquer le trait Ord qu’aux types qui implémentent également 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 dérivée de partial_cmp avec PartialOrd.
Un exemple où Ord est requis est lors du stockage de valeurs dans un BTreeSet<T>, une structure de données qui stocké les données en fonction de l’ordre de tri des valeurs.
Clone et Copy pour dupliquer des valeurs
Le trait Clone vous permet de créer explicitement une copie en profondeur d’une valeur, et le processus de duplication peut impliquer l’exécution de code arbitraire et la copie de données du tas. Consultez la section « Les variables et les données interagissant avec Clone » du chapitre 4 pour plus d’informations sur Clone.
Dériver Clone implémente la méthode clone, qui, lorsqu’elle est implémentée pour le type entier, appelle clone sur chacune des parties du type. Cela signifie que tous les champs ou valeurs du type doivent également implémenter Clone pour pouvoir dériver Clone.
Un exemple où Clone est requis est lors de l’appel de la méthode to_vec sur une slice. La slice ne possède pas les instances du type qu’elle contient, mais le vecteur retourné par to_vec devra posséder ses instances, donc to_vec appelle clone sur chaque élément. Ainsi, le type stocké dans la slice doit implémenter Clone.
Le trait Copy vous permet de dupliquer une valeur en copiant uniquement les bits stockés sur la pile ; aucun code arbitraire n’est nécessaire. Consultez la section « Les données uniquement sur la pile : Copy » du chapitre 4 pour plus d’informations sur Copy.
Le trait Copy ne définit aucune méthode afin d’empêcher les développeurs de surcharger ces méthodes et de violer l’hypothèse qu’aucun code arbitraire n’est exécuté. De cette façon, tous les développeurs peuvent supposer que la copie d’une valeur sera très rapide.
Vous pouvez dériver Copy sur tout type dont toutes les parties implémentent Copy. Un type qui implémente Copy doit également implémenter Clone car un type qui implémente Copy à une implémentation triviale de Clone qui effectue la même tâche que Copy.
Le trait Copy est rarement requis ; les types qui implémentent Copy disposent d’optimisations, ce qui signifie que vous n’avez pas besoin d’appeler clone, ce qui rend le code plus concis.
Tout ce qui est possible avec Copy peut également être accompli avec Clone, mais le code pourrait être plus lent ou devoir utiliser clone par endroits.
Hash pour associer une valeur à une valeur de taille fixe
Le trait Hash vous permet de prendre une instance d’un type de taille arbitraire et d’associer cette instance à une valeur de taille fixe en utilisant une fonction de hachage. Dériver Hash implémente la méthode hash. L’implémentation dérivée de la méthode hash combine le résultat de l’appel de hash sur chacune des parties du type, ce qui signifie que tous les champs ou valeurs doivent également implémenter Hash pour pouvoir dériver Hash.
Un exemple où Hash est requis est lors du stockage des clés dans un HashMap<K, V> pour stocker des données efficacement.
Default pour les valeurs par défaut
Le trait Default vous permet de créer une valeur par défaut pour un type. Dériver Default implémente la fonction default. L’implémentation dérivée de la fonction default appelle la fonction default sur chaque partie du type, ce qui signifie que tous les champs ou valeurs du type doivent également implémenter Default pour pouvoir dériver Default.
La fonction Default::default est couramment utilisée en combinaison avec la syntaxe de mise à jour de structure abordée 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 puis définir et utiliser une valeur par défaut pour le reste des champs en utilisant ..Default::default().
Le trait Default est requis lorsque vous utilisez la méthode unwrap_or_default sur des instances d’Option<T>, par exemple. Si l’Option<T> est None, la méthode unwrap_or_default retournera le résultat de Default::default pour le type T stocké dans l’Option<T>.