Recherche…
Introduction
Les traits sont un moyen de décrire un «contrat» qu'une struct
doit implémenter. Les caractères définissent généralement des signatures de méthode mais peuvent également fournir des implémentations basées sur d'autres méthodes du trait, à condition que les limites de trait le permettent.
Pour ceux qui sont familiers avec la programmation orientée objet, les traits peuvent être considérés comme des interfaces avec quelques différences subtiles.
Syntaxe
- trait Trait {méthode fn (...) -> ReturnType; ...}
- trait Trait: Bound {fn method (...) -> ReturnType; ...}
- impliquer Trait for Type {fn method (...) -> ReturnType {...} ...}
- implicite <T> Trait pour T où T: Bounds {méthode fn (...) -> ReturnType {...} ...}
Remarques
- Les traits sont généralement assimilés à des interfaces, mais il est important de faire la distinction entre les deux. Dans les langages OO tels que Java, les interfaces font partie intégrante des classes qui les étendent. Dans Rust, le compilateur ne sait rien des traits d'une structure à moins que ces traits ne soient utilisés.
Les bases
Créer un trait
trait Speak {
fn speak(&self) -> String;
}
Implémenter un trait
struct Person;
struct Dog;
impl Speak for Person {
fn speak(&self) -> String {
String::from("Hello.")
}
}
impl Speak for Dog {
fn speak(&self) -> String {
String::from("Woof.")
}
}
fn main() {
let person = Person {};
let dog = Dog {};
println!("The person says {}", person.speak());
println!("The dog says {}", dog.speak());
}
Envoi statique et dynamique
Il est possible de créer une fonction qui accepte des objets qui implémentent un trait spécifique.
Envoi statique
fn generic_speak<T: Speak>(speaker: &T) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person);
generic_speak(&dog);
}
La répartition statique est utilisée ici, ce qui signifie que le compilateur Rust générera des versions spécialisées de la fonction generic_speak
pour les types Dog
et Person
. Cette génération de versions spécialisées d'une fonction polymorphe (ou de toute entité polymorphe) au cours de la compilation s'appelle Monomorphization .
Envoi dynamique
fn generic_speak(speaker: &Speak) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person as &Speak);
generic_speak(&dog); // gets automatically coerced to &Speak
}
Ici, une seule version de generic_speak
existe dans le fichier binaire compilé, et l'appel speak()
est effectué à l'aide d'une recherche vtable à l'exécution. Ainsi, l'utilisation de la distribution dynamique accélère la compilation et réduit la taille des fichiers binaires compilés, tout en étant légèrement plus lente à l'exécution.
Les objets de type &Speak
ou Box<Speak>
sont appelés objets de trait .
Types associés
- Utilisez le type associé lorsqu'il existe une relation biunivoque entre le type implémentant le trait et le type associé.
- Il est parfois également appelé type de sortie , car il s’agit d’un élément donné à un type lorsque nous lui appliquons un trait.
Création
trait GetItems {
type First;
// ^~~~ defines an associated type.
type Last: ?Sized;
// ^~~~~~~~ associated types may be constrained by traits as well
fn first_item(&self) -> &Self::First;
// ^~~~~~~~~~~ use `Self::` to refer to the associated type
fn last_item(&self) -> &Self::Last;
// ^~~~~~~~~~ associated types can be used as function output...
fn set_first_item(&mut self, item: Self::First);
// ^~~~~~~~~~~ ... input, and anywhere.
}
Mise en œuvre
impl<T, U: ?Sized> GetItems for (T, U) {
type First = T;
type Last = U;
// ^~~ assign the associated types
fn first_item(&self) -> &Self::First { &self.0 }
fn last_item(&self) -> &Self::Last { &self.1 }
fn set_first_item(&mut self, item: Self::First) { self.0 = item; }
}
impl<T> GetItems for [T; 3] {
type First = T;
type Last = T;
fn first_item(&self) -> &T { &self[0] }
// ^ you could refer to the actual type instead of `Self::First`
fn last_item(&self) -> &T { &self[2] }
fn set_first_item(&mut self, item: T) { self[0] = item; }
}
Référencement aux types associés
Si nous sommes sûrs qu'un type T
implémente GetItems
par exemple dans les génériques, nous pourrions simplement utiliser T::First
pour obtenir le type associé.
fn get_first_and_last<T: GetItems>(obj: &T) -> (&T::First, &T::Last) {
// ^~~~~~~~ refer to an associated type
(obj.first_item(), obj.last_item())
}
Sinon, vous devez indiquer explicitement au compilateur la caractéristique implémentée par le type
let array: [u32; 3] = [1, 2, 3];
let first: &<[u32; 3] as GetItems>::First = array.first_item();
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [u32; 3] may implement multiple traits which many
// of them provide the `First` associated type.
// thus the explicit "cast" is necessary here.
assert_eq!(*first, 1);
Contrainte avec les types associés
fn clone_first_and_last<T: GetItems>(obj: &T) -> (T::First, T::Last)
where T::First: Clone, T::Last: Clone
// ^~~~~ use the `where` clause to constraint associated types by traits
{
(obj.first_item().clone(), obj.last_item().clone())
}
fn get_first_u32<T: GetItems<First=u32>>(obj: &T) -> u32 {
// ^~~~~~~~~~~ constraint associated types by equality
*obj.first_item()
}
Méthodes par défaut
trait Speak {
fn speak(&self) -> String {
String::from("Hi.")
}
}
La méthode sera appelée par défaut sauf si elle est écrasée dans le bloc impl
.
struct Human;
struct Cat;
impl Speak for Human {}
impl Speak for Cat {
fn speak(&self) -> String {
String::from("Meow.")
}
}
fn main() {
let human = Human {};
let cat = Cat {};
println!("The human says {}", human.speak());
println!("The cat says {}", cat.speak());
}
Sortie:
L'humain dit bonjour.
Le chat dit Miaou.
Placer une borne sur un trait
Lors de la définition d'un nouveau trait, il est possible d'imposer aux types souhaitant implémenter ce trait de vérifier un certain nombre de contraintes ou de limites.
Prenant un exemple de la bibliothèque standard, le trait DerefMut
exige qu’un type implémente d’abord son trait de caractère Deref
:
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Cela permet à DerefMut
d'utiliser le type de Target
associé défini par Deref
.
Bien que la syntaxe puisse rappeler l'héritage:
- il apporte tous les éléments associés (constantes, types, fonctions, ...) du trait lié
- il permet le polymorphisme de
&DerefMut
à&Deref
C'est différent dans la nature:
- il est possible d'utiliser une durée de vie (telle que
'static
) - il n'est pas possible de remplacer les éléments de trait liés (même pas les fonctions)
Il est donc préférable de le considérer comme un concept distinct.
Plusieurs types d'objet lié
Il est également possible d'ajouter plusieurs types d'objet à une fonction de distribution statique .
fn mammal_speak<T: Person + Dog>(mammal: &T) {
println!("{0}", mammal.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
mammal_speak(&person);
mammal_speak(&dog);
}