Generische Typen und wichtige Anwendungen
Eines der zentralen Konzepte zum Vermeiden von Codeduplikation sind generische Typen.
Beispielsweise kann man eine Funktion oder einen Typen generisch definieren anstatt
für einen konkreten Typen wie f32
um diese auch mit f64
verwenden zu können.
Der uns bereits bekannte Typ Vec
hat einen generischen Parameter T
der angibt, welchen Typ die Elemente
des Vektors haben. Generische Typparameter schreibt man wie generische Liftime-Parameter in spitze Klammern.
Dementsprechend ist Vec<f32>
ein Vektor mit Elementen des Typs f32
und Vec<f64>
ein Vektor mit Elementen des Typs f64
. Auch die primitiven zusammengesetzten
Typen Array [T; n]
und Tupel (T1, T2)
haben einen generischen Typparameter. Deren Typannotation
verwendet also keine Spitze Klammern. Z.B. ist [u8; 5]
ein 5-elementiger Array natürlicher
Zahlen bis 255. Das Tupel (String, bool, f64)
hat eine Zeichenkette als ersten Typ,
einen Wahrheitswert als zweiten Typ und eine Gleitkommazahl als dritten Typ.
Im Folgenden wollen wir unser Point
-Beispiel aus dem Grundlagenkapitel generalisieren.
struct Point { x: f64, y: f64 } impl Point { fn squared_dist_to_0(&self) -> f64 { self.x * self.x + self.y * self.y } } fn main() { let v = Point{ x: 1.0, y: 1.0 }; let subtraction = v.squared_dist_to_0(); }
Point
und seine Methode length
sind nur für den Typ f64
definiert. Ein f32
-Point
ergibt aber genauso Sinn.
Alles zu duplizieren und nur f64
durch
f32
zu ersetzen, ist keine elegante Option. Wir können einen generischen Typen deklarieren.
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn squared_dist_to_0(&self) -> T { self.x * self.x + self.y * self.y } } fn main() { let v = Point{ x: 1.0, y: 1.0 }; v.squared_dist_to_0(); }
Wenn wir den obigen Schnipsel ausführen wollen, beschwert sich der Compiler, der Typ T
unterstütze weder Multiplikation
noch Addition. Da wir wissen, dass f64
Multiplikation unterstützt, können wir eine spezielle Implementierung für f64
bereit stellen.
struct Point<T> { x: T, y: T, } impl Point<f64> { fn squared_dist_to_0(&self) -> f64 { self.x * self.x + self.y * self.y } } fn main() { let v = Point{ x: 1.0, y: 1.0 }; v.squared_dist_to_0(); let v: Point<i32> = Point{ x: 0, y: 1 }; // v.squared_dist_to_0(); existiert nicht }
Das ist jedoch nur eine Verschiebung des Problems, denn für f32
müssten wir squared_dist_to_0
ebenfalls separat
implementieren.
Wir brauchen eine allgemeine Bedingung des generischen Typs, die nur diejenigen konkreten Typen erlaubt, die
diese Bedingung erfüllen. Daher werden wir
im nächsten Abschnitt über Traits sprechen.