Exemple de code:
trait Animal {
fn marcher(&self);
}
struct Mouton {
}
impl Mouton {
fn beler(&self) {
println!("*Bêlement*");
}
}
impl Animal for Mouton {
fn marcher(&self) {
println!("*Bruits de pas dans l'herbe*");
}
}
struct Cheval {
}
impl Animal for Cheval {
fn marcher(&self) {
println!("*Bruits de pas sur la route*");
}
}
fn faire_marcher_animal(animal: Box<dyn Animal>) {
animal.marcher();
}
Je définis d’abord un trait Animal
qui indique que les types implémentant le trait doivent implémenter une méthode fn marcher(&self);
.
Je créé un type Mouton qui sait marcher et bêler, et un type Cheval qui sait marcher (tous deux implémentent le trait Animal).
Ensuite, je définis une fonction faire_marcher_animal
qui prend un animal (n’importe lequel) et qui le fait marcher. Le type que j’indique ici est Box<dyn Animal>
, cela indique que j’attends un pointeur heap (Box
) qui fait référence à un objet implémentant le trait Animal
.
Si mon argument n’était pas contenu dans un pointeur heap, je ne pourrais pas juste faire passer mon animal sur la stack car les types Mouton et Cheval ont potentiellement des structures mémoire de tailles différentes ; Rust ne devrait donc pas accepter que j’écrive juste dyn Animal
.
Le fait que j’écrive dyn Animal
indique que j’attends un objet qui implémente le trait Animal
(donc qui implémente la méthode fn marcher(&self)
). Néanmoins je ne peux pas être sûr que mon animal est un Mouton, je ne peux donc pas le faire bêler après le passage en argument.
Si je mettais Mouton
ou bien Cheval
à la place de dyn Animal
, je pourrais le faire bêler dans le cas du Mouton, mais je ne pourrais pas alors accepter le Cheval (ou alors, il faudrait que je créé une enum
qui puisse contenir à la fois Mouton et Cheval, et que j’extraie ensuite le Mouton ou le Cheval avec une construction match
, mais dans ce cas le trait ne me sera plus utile..).