Typy ogólne (generics) pozwalają pisać kod niezależny od konkretnego typu. Realizowane są za pomocą składni nawiasów kątowych:
fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } }
Tutaj T to typ ogólny ograniczony traitem PartialOrd.
Parametry generyczne są deklarowane poprzez <T>, ale można je ograniczać za pomocą ograniczeń traitów przez dwukropek, na przykład <T: Display>. To sposób, aby poinformować kompilator, że tylko te typy, dla których zrealizowano odpowiedni trait, mogą być używane.
W Rust wyróżnia się dwie formy dyspozycji dla generics:
Wpływ na kod maszynowy: Użycie generyków z ograniczeniami traitów (bez dyn Trait) prowadzi do monomorfizacji: zwiększenia rozmiaru binarnego, ale maksymalnej prędkości. Użycie dyn Trait oszczędza rozmiar binarny, ale wprowadza spadek wydajności.
Pytanie: Jest funkcja
fn do_something<T: Debug>(value: &T)
Czy kompilator stworzy osobną funkcję do_something w kodzie binarnym dla każdego typu, z którym jest używana, czy użyje ogólnej implementacji?
Typowa błędna odpowiedź: Użyje jednej funkcji dla wszystkich typów dzięki ograniczeniu traitów.
Poprawna odpowiedź: Kompilator tworzy osobne kopie tej funkcji dla każdego typu (monomorfizacja), ponieważ ograniczenie traitu nie czyni funkcji generycznej "uniwersalną" za pomocą vtable. Uniwersalność pojawia się tylko przy dyn Trait (dynamicznej dyspozycji).
Przykład:
fn print_val<T: std::fmt::Debug>(val: T) { println!("{:?}", val); } // Dla każdego wywołania z innym typem zostanie stworzona własna wersja funkcji
Historia
W projekcie z dużymi obiektami generycznymi odkryto, że plik binarny stał się znacznie większy, niż oczekiwano. Później okazało się, że powodem był szeroki użytek funkcji ogólnych bez ograniczeń. Wywołania z dziesiątkami typów doprowadziły do wykładniczego wzrostu rozmiaru pliku wykonywalnego (code bloat), co ujawniono tylko podczas wersji na CI.
Historia
Jeden z programistów przyjmował parametr generyczny z ograniczeniem traitu, sądząc, że taki kod działa z "dynamiczną" dyspozycją. Doprowadziło to do nadmiernego zużycia pamięci na serwerze i spadku wydajności z powodu ciągłego wzrostu kodu i jego buforowania przez procesor.
Historia
W bibliotece próbowano użyć ogólnego traitu z typem Self (na przykład trait Clone) jako dyn Trait, co nie jest wspierane w Rust i doprowadziło do błędu kompilacji. Należało wyraźnie przepisać interfejs, w przeciwnym razie ogólne API nie zadziałałoby w trybie dynamicznym, a interfejs musiałby zostać zmieniony na poziomie kompilacji.