std-Erweiterung
Eine mächtige Eigenschaft von Traits erlaubt es uns, die Funktionalität anderer Bibliotheken zu erweitern Wir untersuchen das an einem Beispiel aus der Standardbibliothek.
Ein hässliches Programm?
Betrachte den folgenden Code:
fn main() {
let nums = [3, 8, 5, 4, 0, 9, 1, 6];
let mut vec = nums
.iter()
.copied()
.filter(|n| n % 2 == 0)
.map(|n| n * 10)
.collect::<Vec<_>>();
vec.sort();
println!("{vec:?}");
}
Hier sieht man das sogenannt "method-chaining", das von Rust unterstützt wird. Auf dem Rückgabewert einer Methode kann wiederum die nächste Methode aufgerufen werden. Wenn Methoden gut durchdacht sind, dann lassen sie sich in langen Ketten aneinanderhängen. Viele Programmierer finden das schön und lesbar.
Was passiert in diesem Beispiel genau?
- aus einem Array werden Zahlen kopiert
- die geraden Zahlen werden herausgefiltert
- der rest wird je mit 10 multipliziert
- das resultat wird in einen Vektor gesammelt
Zuletzt wird der Vektor sortiert. Das passiert aber ausserthalb der "Methodenkette". Wäre es nicht schön, wenn es anders wäre?
Vec erweitern
Können wir dem Vektor eine neue Methode geben? Eigentlich hätten wir gerne sowas:
impl Vec<i32> {
fn sorted(self) -> Self {
todo!()
}
}
Ganz so einfach ist es leider nicht. Der Compiler reklamiert mit:
cannot define inherent
implfor foreign type
Also, freie methoden können wir nicht einfach zu Typen hinzufügen, die wir nicht selber definiert haben. Aber: Über einen Trait geht das!
Zuerst definieren wir also einen Trait, der ausdrückt: "Dieser Typ kann in einer Methodenkette sortiert werden". So könnte das aussehen:
trait Sorted {
fn sorted(self) -> Self;
}
Nun implementieren wir diesen Typ für Vectoren (vorläufig nur von Zahlen, ist einfacher):
impl Sorted for Vec<i32> {
fn sorted(mut self) -> Self {
self.sort();
self
}
}
Einfach, was? Die Methode sortiert den Vector und gibt ihn wieder zurück. Jetzt haben Vectoren eine Methode, welche von der signatur her in einer Methodenkette verwendet werden kann. Versuchen wir es:
fn main() {
let nums = [3, 8, 5, 4, 0, 9, 1, 6];
let vec = nums
.iter()
.copied()
.filter(|n| n % 2 == 0)
.map(|n| n * 10)
.collect::<Vec<_>>()
.sorted();
println!("{vec:?}");
}
Fazit
In diesem Beispiel ging es nur um Ästhetik. Das ist eigentlich nicht so wichtig (mMn).
Allerdings kannst du dir bestimmt vorstellen, wie diese Eigenschaft von Traits es ermöglicht, die Typen der Standardbibliothek mit komplett unabhängigen Bibliotheken wunderbar kompatibel zu machen. Und noch mehr: Bibliotheken, die voneinenader wissen können untereinander ebenfall ohne viel Mühe Kompatibilität erreichen. Als Konsumenten von Bibliotheken profitieren wir schlussendlich von einer äusserst modularen Bibliothekenlandschaft.