Les traits en Rust sont un moyen de définir une interface : ils décrivent un ensemble de méthodes qui doivent être implémentées par un type. L'implémentation d'un trait permet d'utiliser des génériques et le dispatch dynamique des méthodes.
Dispatch statique (par le biais de génériques et de bounds):
Une implémentation spécifique d'une méthode est appelée au moment de la compilation. Un code générique est utilisé (impl<T: Trait> ou fn foo<T: Trait>(t: T)), une version distincte de la fonction est créée pour chaque type (monomorphisation).
Dispatch dynamique :
Utilisé lorsque la fonction prend des objets comme références à un trait (&dyn Trait), le compilateur ne sait pas à l'avance quel type sera utilisé. L'appel de la méthode se fait via une vtable (table virtuelle) à l'exécution.
Exemple:
trait Animal { fn speak(&self); } struct Dog; impl Animal for Dog { fn speak(&self) { println!("Woof!"); } } // Dispatch statique def print_animal_static<T: Animal>(a: T) { a.speak(); } // Dispatch dynamique def print_animal_dyn(a: &dyn Animal) { a.speak(); }
Quand utiliser :
Peut-on stocker des valeurs avec un type générique trait dans Vec<Trait>? Pourquoi?
Réponse :
Non, ce n'est pas possible. Vec exige des types biaisés, et les traits n'ont pas de taille propre. Il faut utiliser Box<dyn Trait> ou des références aux traits.
// let v: Vec<Trait> = vec![]; // erreur de compilation let v: Vec<Box<dyn Animal>> = vec![Box::new(Dog)]; // correct
Histoire
Tentative de créer une collection d'objets trait sans utiliser Box ou Rc :
// let coll: Vec<MyTrait> = vec![]; // erreur : la taille pour les valeurs de type ... ne peut pas être connue
Histoire
Histoire
Dans une démo client, ils ont essayé de stocker des références à des objets temporaires (&dyn Trait) pour interagir avec différents protocoles, menant à des références pendantes et des panic à l'exécution. Il a fallu passer à Box<dyn Trait> avec propriété explicite.