Dynamika wywoływania to mechanizm wyboru konkretnej funkcji (metody) do wywołania. W Rust istnieją dwa podejścia: statyczne i dynamiczne wywoływanie.
Historia zagadnienia:
W językach z OOP do dynamicznego wywołania metody zazwyczaj wykorzystuje się vtable (tablica wirtualna). W Rust podobne rozwiązanie jest stosowane dla obiektów trait - odniesienia do obiektów typów implementujących określone traity. Statyczne wywoływanie metod pojawia się przy użyciu generyków i ograniczeń trait.
Problem:
Często należy wybierać pomiędzy elastycznością (możliwością pracy z obiektami różnych typów przez jeden interfejs) a wydajnością (styczne wywoływanie metod pozwala na inline'owanie). Niepoprawny wybór prowadzi do zbyt skomplikowanych generyków lub do strat wydajności.
Rozwiązanie:
Statyczne wywoływanie osiąga się poprzez parametry generyczne: w tym przypadku kompilator generuje oddzielny kod dla każdego typu. Dynamiczne - jeśli funkcja przyjmuje argument typu &dyn Trait lub Box<dyn Trait>, to przy wywołaniu metody przez trait Rust odwołuje się do vtable pod adresem, jak w klasycznych językach OOP.
Przykład kodu:
trait Shape { fn area(&self) -> f64; } impl Shape for Circle { fn area(&self) -> f64 { 3.1415 * self.radius * self.radius } } fn print_area(shape: &dyn Shape) { // dynamic dispatch println!("area = {}", shape.area()); } // Lub statycznie: fn print_area_static<S: Shape>(shape: &S) { println!("area = {}", shape.area()); }
Kluczowe cechy:
dyn Trait używa vtable (dynamiczne wywoływanie)Czy można zrobić Box<dyn Sized>?
Nie. dyn Trait z definicji jest unsized, zawsze wymaga użycia Box, Arc lub referencji, ale nie „Box<dyn Sized>” - to nie ma sensu. Traity Sized nie mają obiekty trait.
Czy dyn Trait jest dozwolony dla traitów z generycznymi metodami?
Nie. Nie można stworzyć traitów bezpiecznych dla obiektów z generycznymi metodami (ludzie często to mylą!), typy złożone nie są bezpieczne dla obiektów:
trait MyTrait { fn foo<T>(&self, x: T); } let x: &dyn MyTrait = ... // Błąd kompilacji!
Czy można zrobić dyn Trait dla traita z wartościami Self w sygnaturze?
Nie, jeśli metoda zwraca Self (wielu nie rozumie tego szczegółu: bezpieczeństwo obiektów wymaga, aby sygnatura nie zawierała Self; można self tylko w argumentach, ale nie w wartościach zwracanych).
Używano wszędzie dyn Trait dla uniwersalności interfejsów, nawet wewnątrz tight loops, gdzie można było obejść się bez generics.
Plusy:
Minusy:
Statyczne wywoływanie było stosowane wewnętrznej logice, a dyn Trait tylko na granicach modułów.
Plusy:
Minusy: