Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Annexe C : traits dérivables

À plusieurs reprises dans le Livre, nous avons mentionné l’attribut derive, que vous pouvez appliquer à la définition d’une structure ou d’une é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 fournissons une référence de tous les traits de la bibliothèque standard que vous pouvez utiliser avec derive. Chaque partie couvre :

  • ce que permettront les opérateurs et les méthodes dérivés de ce trait ;
  • ce que fait l’implémentation du trait fourni par derive ;
  • ce que l’implémentation du trait implique en ce qui concerne le type ;
  • les conditions dans lesquelles vous pouvez ou non implémenter le trait ;
  • exemples d’opérations qui nécessitent le trait.

Si vous souhaitez avoir un comportement différent de celui fourni par l’attribut derive, consultez la documentation de la bibliothèque standard pour chaque trait, pour avoir les détails de la manière de les implémenter manuellement.

Les traits listés ici sont les seuls à être 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 logique par défaut, c’est donc à vous de les implémenter de la manière la plus adaptée à ce que vous cherchez à faire.

Un exemple de trait qui ne peut pas être dérivé est Display, lequel gère le formatage à destination des utilisateurs finaux. Vous devriez toujours vous demander quelle est la manière la plus appropriée d’afficher un type à un utilisateur final. Quelles parties du type peuvent être vues par un utilisateur final ? Quelles parties trouveraient-ils pertinentes ? Quel formatage des données serait le plus pertinent pour eux ? Le compilateur Rust n’a pas cette intuition, il ne peut donc point fournir à votre place le comportement par défaut le plus adéquat.

La liste des traits dérivables fournie dans cette annexe n’est pas exhaustive : des bibliothèques peuvent implémenter derive pour leurs propres traits, étendant potentiellement à l’infini la liste des traits que vous pouvez utiliser avec derive. L’implémentation de derive implique l’utilisation d’une macro procédurale, ce dont il est question dans la section “Macros personnalisées derive du chapitre 20.

Debug pour l’affichage à destination du programmeur

Le trait Debug permet le formatage de débogage pour mettre en forme les valeurs comme des chaînes de caractères, que vous pouvez utiliser 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, de manière à ce que vous et les autres programmeurs utilisant votre type puissent inspecter une instance à un endroit précis de l’exécution du programme.

Le trait Debug est, par exemple, nécessaire pour l’utilisation de la macro assert_eq!. Cette macro affiche les valeurs d’instances passées en argument dans le cas où l’affirmation d’égalité échoue, de manière à ce que les programmeurs puissent voir pourquoi les deux instances ne sont pas égales.

PartialEq et Eq pour comparaisons d’égalité

Le trait PartialEq vous permet de comparer des instances d’un type afin de vérifier leur égalité et permet l’utilisation des opérateurs == et !=.

La dérivation de PartialEq implémente la méthode eq. Quand PartialEq est dérivée sur des structures, deux instances sont égales si et seulement si tous les champs sont égaux, et les instances ne sont pas égales si ne serait-ce qu’un seul champ n’est pas égal. Quand ce trait est dérivé sur des énumérations, chaque variante est égale à elle-même et n’est pas égale aux autres variantes.

Le trait PartialEq est, par exemple, nécessaire pour l’utilisation de la macro assert_eq!, qui doit pouvoir comparer l’égalité de deux instances d’un type.

Le trait Eq n’a aucune méthode. Son rôle est de signaler, pour chaque valeur du type annoté, que la valeur est égale à elle-même. Le trait Eq ne peut être appliqué qu’aux types qui implémentent aussi PartialEq, quoique tous les types implémentant PartialEq ne puissent pas nécessairement implémenter Eq. Les nombres à virgule flottante en sont un exemple : l’implémentation des nombres à virgule flottante stipule que deux instances de la valeur “not-a-number” (NaN, c’est-à-dire “ceci n’est pas un nombre”) ne sont pas égales entre elles.

Un exemple de cas où Eq est nécessaire est celui des clés dans une table de hachage HashMap<K, V>, de sorte que la table 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 pour pouvoir les trier. Un type implémentant PartialOrd peut être utilisé avec les opérateurs <, >, <= et >=. Vous ne pouvez appliquer le trait PartialOrd qu’aux types qui implémentent aussi PartialEq.

Dériver de PartialOrd implémente la méthode partial_cmp, laquelle renvoie une 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 en virgule flottante non-nombre NaN. Appeler partial_cmp avec n’importe quel nombre à virgule flottante et la valeur NaN de virgule flottante renverra None.

Quand 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. Quand il est dérivé sur des énumérations, les variantes de l’énumération précédemment déclarées dans la définition de l’énumération sont considérées comme inférieures aux variantes déclarées après.

Le trait PartialOrd est obligatoire, par exemple, pour la méthode gen_range de la crate rand, qui génère une valeur aléatoire dans l’intervalle spécifié par une valeur minimale et une valeur maximale.

Le trait Ord permet de savoir si, pour deux valeurs du type annoté, un tri valide existera. Le trait Ord implémente la méthode cmp, laquelle renvoie un Ordering au lieu d’une Option<Ordering> car un tri valide sera toujours possible. Vous pouvez uniquement appliquer le trait Ord sur les types qui implémentent aussi PartialOrd et Eq (et Eq requiert à son tour PartialEq). Quand 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 pour partial_cmp le fait pour PartialOrd.

Un exemple de cas où Ord est obligatoire est quand on stocke des valeurs dans un BTreeSet<T>, une structure de données qui enregistre des données en fonction de l’ordre de tri de ces valeurs.

Clone et Copy pour dupliquer des valeurs

Le trait Clone permet de créer de manière explicite une copie profonde d’une valeur, et la procédure de duplication peut impliquer l’exécution de code arbitraire ainsi que la copie de données sur le tas. Voyez la section “Interactions entre variables et données : le clonage” du chapitre 4 pour plus d’informations sur Clone.

Dériver de Clone implémente la méthode clone, laquelle, quand implémentée sur le type complet, fait appel à clone sur chaque constituant du type. Ceci signifie que tous les champs ou les valeurs dans le type doivent également implémenter Clone pour pouvoir dériver de Clone.

Un exemple de cas où Clone est nécessaire est quand on appelle la méthode to_vec sur une slice. La slice ne possède pas les instances du type qu’elle contient, mais le vecteur renvoyé par to_vec aura besoin de posséder ses instances, de sorte que to_vec fait appel à clone sur chaque élément. Conséquemment, le type enregistré dans la slice doit implémenter Clone.

Le trait Copy permet de dupliquer une valeur en copiant seulement les éléments enregistrés dans la pile ; aucun code arbitraire n’est nécessaire. Voyez 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 aucune méthode, volontairement pour empêcher les programmeurs de surcharger ces méthodes et ainsi violer l’affirmation selon laquelle aucun code arbitraire n’est exécuté. Ainsi, tous les programmeurs peuvent présupposer que la copie d’une valeur sera très rapide.

Vous pouvez dériver Copy sur n’importe quel type dont tous les composants implémentent Copy. Un type implémentant Copy doit également implémenter Clone, car un type implémentant Copy a une implémentation triviale de Clone qui effectue le même travail que Copy.

Le trait Copy est rarement nécessaire ; les types qui implémentent Copy disposent d’optimisations, ce qui implique que vous n’avez pas à appeler clone, ce qui rend le code plus concis.

Tout ce qui peut être accompli avec Copy peut aussi être fait avec Clone, mais le code risque d’être plus lent ou pourrait devoir utiliser clone par endroits.

Hash pour faire correspondre une valeur avec une valeur de taille fixe

Le trait Hash vous permet de prendre une instance d’un type de taille quelconque et de la faire correspondre avec une valeur de taille fixe en utilisant une fonction de hachage. Dériver de Hash implémente la méthode hash. L’implémentation dérivée de la méthode hash réunit le résultat de l’appel de hash sur chacune des parties du type, ce qui implique que tous les champs ou les valeurs doivent également implémenter Hash pour dériver Hash.

Un exemple d’un cas requerrant Hash est l’enregistrement de clés dans une table HashMap<K, V> afin de stocker les données de manière efficace.

Default pour des valeurs par défaut

Le trait Default permet de créer une valeur par défaut pour un type. Dériver de Default ajoute la fonction default. L’implémentation dérivée de la fonction default appelle la fonction default sur chaque partie du type, ce qui implique que tous les champs ou les valeurs doivent également implémenter Default pour dériver Default.

La fonction Default::default est couramment utilisée en association avec la syntaxe de modification de structure, que nous avons vue dans la section “Créer des instances avec la syntaxe de mise à jour de structure” du chapitre 5. Vous pouvez personnaliser quelques champs d’une structure puis ensuite définir et utiliser une valeur par défaut pour le reste des champs en utilisant ..Default::default().

Le trait Default trait est nécessaire lorsque vous utilisez la méthode unwrap_or_default sur des instances d’Option<T>, par exemple. Si l’Option<T> vaut None, la méthode unwrap_or_default renverra le résultat de Default::default sur le type T stocké dans l’Option<T>.