Cechy w Rust to sposób definiowania interfejsu: opisują zestaw metod, które typ musi zaimplementować. Implementacja cechy pozwala korzystać z generyków i dynamicznego wyszukiwania metod.
Statyczne wyszukiwanie (przez generyki i boundy):
Konkretną implementację metody wywołuje się w czasie kompilacji. Używa się kodu generycznego (impl<T: Trait> lub fn foo<T: Trait>(t: T)), w czasie kompilacji dla każdego typu tworzy się osobna wersja funkcji (monomorfizacja).
Dynamiczne wyszukiwanie:
Używane, gdy funkcja przyjmuje obiekty jako referencje do cechy (&dyn Trait), kompilator nie zna wcześniej, jaki typ zostanie użyty. Wywołanie metody odbywa się przez vtable (wirtualną tabelę) w czasie wykonywania.
Przykład:
trait Animal { fn speak(&self); } struct Dog; impl Animal for Dog { fn speak(&self) { println!("Woof!"); } } // Statyczne wyszukiwanie def print_animal_static<T: Animal>(a: T) { a.speak(); } // Dynamiczne wyszukiwanie def print_animal_dyn(a: &dyn Animal) { a.speak(); }
Kiedy używać:
Czy można przechowywać wartości z generycznym typem cechy w Vec<Trait>? Dlaczego?
Odpowiedź:
Nie, nie można tak zrobić. Vec wymaga typów o znanych rozmiarach, a cechy z samej natury nie mają rozmiaru. Należy użyć Box<dyn Trait> lub referencji do cech.
// let v: Vec<Trait> = vec![]; // błąd kompilacji let v: Vec<Box<dyn Animal>> = vec![Box::new(Dog)]; // poprawnie
Historia
Próba stworzenia kolekcji obiektów cechy bez użycia Box lub Rc:
// let coll: Vec<MyTrait> = vec![]; // błąd: rozmiar dla wartości typu ... nie może być znany
Historia
Historia
Na demonstracyjnym kliencie próbowano przechowywać referencje do tymczasowych obiektów (&dyn Trait) w celu interakcji z różnymi protokołami, co prowadziło do dangling references i runtime panic. Musiano przejść na Box<dyn Trait> z wyraźnym posiadaniem.