#14 Traits
trait, impl, dyn
Qu'est-ce qu'un trait ?
Un trait définit un ensemble de comportements (méthodes) qu'un type peut implémenter. C'est similaire aux interfaces dans d'autres langages comme Java ou TypeScript.
Les traits permettent le polymorphisme : différents types peuvent partager le même comportement tout en ayant des implémentations différentes.
Définir des traits
On définit un trait avec le mot-clé trait, suivi des signatures des méthodes.
trait Forme {
fn aire(&self) -> f64;
fn perimetre(&self) -> f64;
fn nom(&self) -> &str;
}Implémenter des traits
On implémente un trait pour un type avec impl Trait for Type. Chaque méthode du trait doit être définie.
struct Cercle {
rayon: f64,
}
struct Rectangle {
largeur: f64,
hauteur: f64,
}
impl Forme for Cercle {
fn aire(&self) -> f64 {
std::f64::consts::PI * self.rayon * self.rayon
}
fn perimetre(&self) -> f64 {
2.0 * std::f64::consts::PI * self.rayon
}
fn nom(&self) -> &str {
"Cercle"
}
}
impl Forme for Rectangle {
fn aire(&self) -> f64 {
self.largeur * self.hauteur
}
fn perimetre(&self) -> f64 {
2.0 * (self.largeur + self.hauteur)
}
fn nom(&self) -> &str {
"Rectangle"
}
}
fn main() {
let c = Cercle { rayon: 5.0 };
let r = Rectangle { largeur: 4.0, hauteur: 6.0 };
println!("{} : aire = {:.2}", c.nom(), c.aire());
println!("{} : aire = {}", r.nom(), r.aire());
}Méthodes par défaut
Un trait peut fournir une implémentation par défaut pour certaines méthodes. Les types qui implémentent le trait peuvent utiliser la version par défaut ou la remplacer.
trait Resumable {
fn auteur(&self) -> &str;
fn contenu(&self) -> &str;
// Méthode par défaut
fn resume(&self) -> String {
format!("{} — {}", self.auteur(), self.contenu())
}
}
struct Article {
auteur: String,
titre: String,
}
impl Resumable for Article {
fn auteur(&self) -> &str {
&self.auteur
}
fn contenu(&self) -> &str {
&self.titre
}
// Remplacer la méthode par défaut
fn resume(&self) -> String {
format!("Article de presse par {}", self.auteur)
}
}
struct Tweet {
utilisateur: String,
texte: String,
}
impl Resumable for Tweet {
fn auteur(&self) -> &str {
&self.utilisateur
}
fn contenu(&self) -> &str {
&self.texte
}
// Utilise la méthode par défaut de resume()
}Trait bounds
Les trait bounds permettent de restreindre les types génériques à ceux qui implémentent un trait donné. On peut utiliser la syntaxeimpl Trait ou la notation <T: Trait>.
// Syntaxe avec impl Trait (simple)
fn afficher_resume(item: &impl Resumable) {
println!("Résumé : {}", item.resume());
}
// Syntaxe avec trait bound (explicite)
fn afficher_resume_generique<T: Resumable>(item: &T) {
println!("Résumé : {}", item.resume());
}
// Plusieurs trait bounds avec +
fn afficher_et_debug<T: Resumable + std::fmt::Debug>(item: &T) {
println!("{:?}", item);
println!("Résumé : {}", item.resume());
}
// Retourner un type qui implémente un trait
fn creer_tweet() -> impl Resumable {
Tweet {
utilisateur: String::from("@rustlang"),
texte: String::from("Rust, c'est super !"),
}
}
fn main() {
let article = Article {
auteur: String::from("Le Monde"),
titre: String::from("Rust élu langage préféré"),
};
let tweet = creer_tweet();
afficher_resume(&article);
afficher_resume(&tweet);
}À vous de jouer
Essayez les commandes ci-dessous pour compiler et exécuter le programme :