Les traits
Un trait décrit un comportement qu’un type peut fournir. C’est le mécanisme d’abstraction central de Rust : il remplace à la fois les interfaces, les classes abstraites et la surcharge d’opérateurs des langages à objets, et il sous-tend la quasi-totalité de la bibliothèque standard — Iterator, Display, Clone, Deref, From sont tous des traits.
Déclarer et implémenter
Le trait déclare la signature, chaque impl ... for ... fournit le corps. La différence essentielle avec une interface classique : le trait et le type sont indépendants. Il est possible d’implémenter son propre trait sur un type de la bibliothèque standard.
Les méthodes par défaut
Un trait peut fournir une implémentation, que les types peuvent accepter telle quelle ou remplacer.
C’est le ressort de la richesse d’Iterator : une seule méthode requise, next(), et une soixantaine de méthodes par défaut définies à partir d’elle.
La règle d’orphelin
Une implémentation n’est permise que si le trait ou le type appartient à votre crate. Implémenter un trait étranger sur un type étranger — par exemple Display sur Vec<T> — est interdit.
Cette règle, dite de cohérence, garantit qu’il ne peut exister qu’une seule implémentation d’un trait pour un type donné dans tout le programme, quelles que soient les dépendances assemblées.
Tip
Le contournement est le patron newtype : envelopper le type étranger dans une structure tuple locale, qui devient un type de votre crate.
derive
L’attribut derive demande au compilateur de générer l’implémentation d’un trait, quand elle est mécaniquement déductible de la structure du type.
C’est strictement équivalent à écrire les blocs impl à la main, en plus court et sans risque d’oubli lors de l’ajout d’un champ. Les traits dérivables courants sont listés dans Structures, énumérations et méthodes.
Les traits comme contraintes génériques
C’est le premier usage : écrire une fonction valable pour tout type fournissant un comportement donné.
T: Surface se lit « pour tout type T implémentant Surface ». Plusieurs contraintes se cumulent avec +, et la clause where allège les signatures chargées :
Info
Ces génériques sont résolus par monomorphisation : le compilateur produit une version spécialisée du code pour chaque type réellement employé. D’où l’absence de surcoût à l’exécution — l’appel est direct, et peut être aligné (inlined).
impl Trait en paramètre
Pour une contrainte simple, impl Trait en position d’argument évite de nommer le paramètre générique :
C’est un sucre syntaxique strictement équivalent à la version <T: Surface>, à une nuance près : le type ne pouvant plus être nommé, il devient impossible de le fixer explicitement à l’appel avec un turbofish.
impl Trait en retour
En position de retour, impl Trait signifie « un type concret unique, que je ne nomme pas ».
C’est indispensable pour les closures et les itérateurs, dont les types réels sont inexprimables. La contrepartie : toutes les valeurs retournées doivent avoir le même type concret, ce qui interdit de renvoyer deux itérateurs différents selon une branche.
Les objets-traits : dyn Trait
Quand le type concret ne peut être connu qu’à l’exécution — une collection hétérogène, une stratégie choisie par configuration — il faut passer par un objet-trait, derrière un pointeur.
L’appel passe alors par une table de méthodes virtuelles, résolue à l’exécution.
Générique <T: Trait> | Objet-trait dyn Trait | |
|---|---|---|
| Résolution | Compilation (monomorphisation) | Exécution (table virtuelle) |
| Coût à l’appel | Nul, alignement possible | Une indirection |
| Taille du binaire | Une copie par type utilisé | Une seule copie |
| Types mélangeables | Non | Oui |
| Usage | Le cas par défaut | Collections hétérogènes, greffons |
Note
Tous les traits ne peuvent pas devenir des objets-traits. Un trait est compatible avec dyn si aucune de ses méthodes ne renvoie Self, n’est générique, ni n’est une fonction associée sans receveur. C’est pourquoi Clone, dont clone() renvoie Self, ne s’emploie pas en dyn Clone :
La documentation plus ancienne parle d’object safety ; le terme a été renommé en dyn compatibility, y compris dans les messages du compilateur.
Types associés
Un trait peut déclarer un type que l’implémenteur devra fixer, plutôt que de le recevoir en paramètre générique.
Le choix entre type associé et paramètre générique tient à une question : un type peut-il implémenter le trait plusieurs fois ?
Iteratora un type associéItem: un type est un itérateur d’une seule sorte d’éléments.From<T>est générique : un même type peut être convertible depuis plusieurs sources, d’oùimpl From<u8> for Nombreetimpl From<u16> for Nombrecôte à côte.
Supertraits
Un trait peut en exiger un autre :
Implémenter Identifiable impose alors d’implémenter aussi Display.
Implémentations générales
Une implémentation peut viser d’un coup tous les types satisfaisant une contrainte. C’est ce que l’on appelle une blanket impl.
La bibliothèque standard en fait un usage massif : impl<T: Display> ToString for T est la raison pour laquelle to_string() existe sur tout type sachant s’afficher.
Les traits à connaître
| Trait | Rôle |
|---|---|
Debug / Display | Affichage de diagnostic {:?} / affichage destiné à l’utilisateur {} |
Clone / Copy | Duplication explicite / copie implicite |
PartialEq / Eq | Égalité |
PartialOrd / Ord | Ordre et tri |
Hash | Emploi comme clé de table associative |
Default | Valeur par défaut |
From / Into | Conversions infaillibles |
TryFrom / TryInto | Conversions faillibles, renvoyant un Result |
Iterator / IntoIterator | Parcours — voir Les itérateurs |
Deref / DerefMut | Coercition — voir Le mécanisme Deref |
Drop | Traitement à la libération |
Fn / FnMut / FnOnce | Appelabilité — voir Les closures |
Send / Sync | Transmissibilité et partage entre fils d’exécution |
Tip
Implémenter From plutôt qu’Into : une implémentation générale de la bibliothèque standard fournit automatiquement l’Into réciproque.
C’est aussi ce qui fait fonctionner la conversion automatique d’erreurs par l’opérateur ?, décrite dans Result et la gestion des erreurs.
À retenir
- Un trait décrit un comportement ; il est déclaré indépendamment des types qui le fournissent.
- Les méthodes par défaut permettent d’exiger peu et d’offrir beaucoup.
- La règle d’orphelin garantit l’unicité des implémentations ; le patron newtype la contourne légitimement.
<T: Trait>résout à la compilation,dyn Traità l’exécution — le premier est le choix par défaut.- Type associé quand l’implémentation est unique, paramètre générique quand elle peut être multiple.
Ressources
- Traits — chapitre du Rust Book
- Trait objects — le chapitre consacré au dispatch dynamique
- Dyn compatibility — les règles de compatibilité avec
dyn std::ops— les traits de surcharge d’opérateurs