Traits in Rust zijn een manier om een interface te definiëren: ze beschrijven een set methoden die door een type moeten worden geïmplementeerd. De implementatie van een trait maakt het mogelijk om generics en dynamische dispatch van methoden te gebruiken.
Statische dispatch (via generics en bounds):
De specifieke implementatie van een methode wordt tijdens de compilatie aangeroepen. Gebruikt generieke code (impl<T: Trait> of fn foo<T: Trait>(t: T)), en voor elk type wordt er tijdens de compilatie een aparte versie van de functie gemaakt (monomorfisatie).
Dynamische dispatch:
Wordt gebruikt wanneer de functie objecten accepteert als verwijzingen naar traits (&dyn Trait), de compiler weet van tevoren niet welk type zal worden gebruikt. De methoden worden aangeroepen via een vtable (virtuele tabel) tijdens runtime.
Voorbeeld:
trait Animal { fn speak(&self); } struct Dog; impl Animal for Dog { fn speak(&self) { println!("Woef!"); } } // Statische dispatch def print_animal_static<T: Animal>(a: T) { a.speak(); } // Dynamische dispatch def print_animal_dyn(a: &dyn Animal) { a.speak(); }
Wanneer te gebruiken:
Kan je waarden met een generiek type trait opslaan in Vec<Trait>? Waarom?
Antwoord:
Nee, dat kan niet. Vec vereist sized-types, en traits hebben zelf geen grootte. Je moet Box<dyn Trait> of verwijzingen naar traits gebruiken.
// let v: Vec<Trait> = vec![]; // compilatiefout let v: Vec<Box<dyn Animal>> = vec![Box::new(Dog)]; // correct
Verhaal
Poging om een collectie van trait-objecten te maken zonder gebruik te maken van Box of Rc:
// let coll: Vec<MyTrait> = vec![]; // fout: de grootte voor waarden van type ... kan niet bekend zijn
Verhaal
Verhaal
Bij de demo klant probeerden ze verwijzingen op te slaan naar tijdelijke objecten (&dyn Trait) voor interactie met verschillende protocollen, wat leidde tot dangling references en runtime panic. Ze moesten overschakelen naar Box<dyn Trait> met expliciet eigenaarschap.