Typy generyczne
Typy genereczne można wykorzystać w definicji sygnatur funkcji, struktur lub typów wyliczeniowych, które można następnie użyć z wieloma różnymi konkretnymi typami danych.
Typy generyczne w definicji struktury
- Zdefiniujmy strukturę zawierającą parę wartości tego samego typu:
struct Pair<T> { x: T, y: T }
T
oznacza dowolny typ. - Teraz możemy utworzyć np. parę liczb całkowitych lub zmiennoprzecinkowych:
fn main() { let pi = Pair{x : 5, y : 3}; let pf = Pair {x: 15f64, y : 12.0f64}; }
- Ale nie możemy utworzyć pary złożonej z liczby całkowitej i rzeczywistej
fn main() { let pw = Pair {x: 15f64, y : 0}; // compile error; x & y must be the same type }
- Zdefiniuj parę, która może mieć wartości rożnego typu.
Typy generyczne w wyliczeniach
- Przykładem wykorzystania typów generycznych w wyliczenia jest wbudowany typ
Option
, który już poznaliśmy. Jego definicja wygląda nastepująco:enum Option<T> { Some(T), None }
- Innym przykładem typu wyliczeniowego zawierającego typ generyczny, który będzie często wykorzystywany jest typ
Result
:enum Result<T, E> { Ok(T), Err(E) }
Typy generyczne w funkcjach i metodach
- Możemy również zdefiniować funkcje operujące na typach generycznych. Przed nami (na razie bez większego sensu) funkcja zwracająca pierwszą współrzędną z pary
Funkcja
fn extract_x<T>(p : Pair<T>) -> T { p.x }
exrtact_x
działa dla dowolnej pary i zwraca wartość typu generycznego.Należy zwrócić uwagę, że podczas kompilacji, funkcja ta jest kompilowana dla każdego wykorzystywanego typu osobno. Przykładowo, jeśli funkcja ta zostanie wywołana z parą posiadającą liczby całkowite
i32
, to kompilator utworzy funkcjęfn extract_x(p : Pair<i32>) -> i32
. - Możemy taką funkcję zamienić na metodę:
Zwróć uwagę na umieszczenie definicji typów generycznych.
impl <T> Pair<T> { fn extract_x(self) -> T { self.x } }
- Metody i funkcje możemy pisać również tylko dla określonych typów, np. dla liczb całkowitych
impl Pair<i32> { fn bigger(&self) -> i32 { if self.x > self.y { self.x } else { self.y } } }
- Sprawdź działanie metodty
bigger
na parzepi
orazpf
. - Spróbuj napisać metodę
bigger
dla dowolnego typu.
Granice cech
-
Aby napisać metodę
bigger
trzeba ograniczyć zestaw typów, do tych posiadających możliwości porównania wartości (PartialOrd
). W tym celu musimy zawęzić typT
:impl<T: PartialOrd> Pair<T> { fn bigger(&self) -> T { if self.x > self.y { self.x } else { self.y } } }
-
Czy kod się kompiluje? Dlaczego?
-
Język Rust pozwala na definicję wielu ograniczeń dot. przyjmowanego typu, wykorzystujemy do tego operator
+
wykonywany na typie:impl<T: PartialOrd + Copy> Pair<T> { fn bigger(&self) -> T { if self.x > self.y { self.x } else { self.y } } }
-
W przypadku funkcji ograniczenia dla wielu typów zapisujemy w następujący sposób:
fn some_function<T: Display + Clone, U : Debug + Clone>(t: T, u: U) -> i32 { ... }
-
Jeśli funkce posiadają wiele parametrów ograniczanych różnymi typami, możemy wykorzystać również bardziej przystępną składnię:
fn some_function<T, U>(t: T, u: U) -> i32 where T : Display + Clone, U : Debug + Clone { ... }
Ćwiczenie
- Napisz funkcję
max
, która zwróci największą wartość tablicy zawierającej dowolne typy liczbowe. Funkcja powinna zwracaćSome(max)
lubNone
w przypadku pustej tablicy. - (*) Napisz funkcję
mean
, która wyznaczy średnią arytmentyczną elementów tablicy zawierającej dowolne typy liczbowe. - Napisz funkcję dodawanie par liczbowych (struktur typu
Pair
).