Home
# Traits --- ## Généralités - Un `Trait` permet de dire au compilateur quelles fonctionnalités un type doit posséder. - Permet de conceptualiser les fonctionnalités partagées entre types. - On peut contraindre un type générique à avoir un certain trait (*trait bound*). ```rust fn max
(a: T, b: T) -> T { if a > b { a } else { b } // si T ne peut pas être ordonné, "<" et ">" n'existent pas } ``` - Similaire aux *interfaces* de certains langages. --- ## Définition d'un trait ```rust [1,4|2|3|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn nom(&self) -> &String; } // le nombre de méthode est arbitraire ``` --- ## Implémentation d'un trait pour un type - Un type qui implémente un trait doit implémenter **toutes** ses méthodes. - Le compilateur sait qu'un type implémentant un trait peut appeler ses méthodes. ```rust [5-7|8-15|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn nom(&self) -> &String; } // le nombre de méthode est arbitraire struct Dog { nom: String, } impl Animal for Dog { // le bloc où les méthodes du trait sont implémentées fn cri(&self) -> String { String::from("Ouaf!") } fn nom(&self) -> &String { &self.nom } } fn main() { let chien = Dog{ nom: String::from("Jack")}; println!("Le cri de {} est {}", chien.nom(), chien.cri()); } ``` --- ## Implémentation d'un trait pour deux types ```rust [15-17|18-25|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn nom(&self) -> &String; } // le nombre de méthode est arbitraire struct Dog { nom: String, } impl Animal for Dog { // le bloc où les méthodes du trait sont implémentées fn cri(&self) -> String { String::from("Ouaf") } fn nom(&self) -> &String { &self.nom } } struct Cat { nom: String, } impl Animal for Cat { // le bloc où les méthodes du trait sont implémentées fn cri(&self) -> String { String::from("Miaou") } fn nom(&self) -> &String { &self.nom } } fn main() { let chien = Dog{ nom: String::from("Jack")}; println!("Le cri de {} est {}.", chien.nom(), chien.cri()); let chat = Cat{ nom: String::from("Rémy")}; println!("Le cri de {} est {}.", chat.nom(), chat.cri()); } ``` --- ## Implémentations par défaut - Pratique d'avoir des implémentations par défaut (les mêmes pour tous les types implémentant un trait). ```rust [1,3-5,6|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn cri_puissant(&self) -> String { self.cri().to_uppercase() } } // le nombre de méthode est arbitraire struct Dog { } impl Animal for Dog { fn cri(&self) -> String { String::from("Ouaf") } } struct Cat { } impl Animal for Cat { // le bloc où les méthodes du trait sont implémentées fn cri(&self) -> String { String::from("Miaou") } } fn main() { let chien = Dog{}; println!("Le cri du chien est {} et son cri puissant est {}.", chien.cri(), chien.cri_puissant()); let chat = Cat{}; println!("Le cri du chat est {} et son cri puissant est {}.", chat.cri(), chat.cri_puissant()); } ``` --- ## Trait bound ("contrainte de trait") - Lors de la définition d'un générique, on peut dire au compilateur si le type implémente un trait. ```rust [1-5|7-9|10-12|] // T implémente PartialOrd (les opérateurs <, >) fn max
(a: T, b: T) -> T { if a > b { a } else { b } // si on peut pas comparer a et b } // cela ne peut pas compiler, d'où // le PartialOrd fn main() { let a = 1; let b = 7; println!("De {} et {}, {} est le plus grand.", a, b, max(a,b)); let a = 1.5; let b = 7.5; println!("De {} et {}, {} est le plus grand.", a, b, max(a,b)); } ``` --- ## Règle pour l'implémentation d'un trait - On peut implémenter un trait seulement si le trait ou le type est local à notre librairie (*crate*). - Impossible de réimplémenter l'addition pour les `f64` (le trait `Add` et le type `f64`) sont dans la librairie standard, donc pas dans une librairie locale.