Generics in Rust: Warum sie wichtig sind und wie man sie als Anfänger verwendet

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Generics in Rust: Warum sie wichtig sind und wie man sie verwendet

Generics (Verallgemeinerungen) sind eines der Schlüsselkonzepte in Rust, die es ermöglichen, flexiblen, wiederverwendbaren und sicheren Code zu schreiben. Wenn Sie gerade erst anfangen, Rust zu lernen, eröffnet das Verständnis von Generics den Weg zum Schreiben effizienter Bibliotheken und Anwendungen. In diesem Artikel werden wir untersuchen, was Generics sind, warum sie benötigt werden und wie man sie in der Praxis anwendet.



Was sind Generics und warum werden sie benötigt?

Stellen Sie sich vor, Sie müssen eine Funktion schreiben, die das maximale Element in einem Array findet. Für Zahlen gibt es eine Implementierung, für Zeichenketten eine andere. Um den Code nicht für jeden Typ zu kopieren, bietet Rust generische Programmierung mit Generics an.

Generics ermöglichen es, Strukturen, Funktionen, Aufzählungen oder Methoden zu definieren, die mit einem beliebigen Datentyp arbeiten, ohne an einen bestimmten gebunden zu sein. Der Compiler setzt während der Kompilierung den tatsächlichen Typ ein – dieser Prozess wird Monomorphisierung genannt.

Ohne Generics würden Sie schreiben:

fn max_i32(list: &[i32]) -> i32 {    let mut max = list[0];    for &item in list {        if item > max {            max = item;        }    }    max}

fn max_f64(list: &[f64]) -> f64 { let mut max = list[0]; for &item in list { if item > max { max = item; } } max}

Mit Generics wird daraus eine einzige universelle Funktion.



Grundlagen der Verwendung von Generics

Generic-Funktionen

Um eine Funktion generisch zu machen, müssen Sie einen Typparameter in spitzen Klammern <T> angeben:

fn max<T>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

Dieser Code wird jedoch nicht kompiliert – der Compiler wird sagen, dass für den Typ T das Trait PartialOrd nicht implementiert ist. Hier kommen Trait-Bounds ins Spiel.



Generic-Strukturen und Aufzählungen

Strukturen können ebenfalls generisch sein:

struct Point<T> {    x: T,    y: T,}

fn main() { let int_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.5, y: 3.8 };}

Sie können mehrere Typparameter angeben:

struct Pair<T, U> {    first: T,    second: U,}

Aufzählungen unterstützen ebenfalls Generics – denken Sie an Option<T> oder Result<T, E> aus der Standardbibliothek.



Generic-Methoden

Für Strukturen mit Generics können Methoden definiert werden:

impl<T> Point<T> {    fn x(&self) -> &T {        &self.x    }}

Oder nur für einen bestimmten Typ:

impl Point<f64> {    fn distance_from_origin(&self) -> f64 {        (self.x.powi(2) + self.y.powi(2)).sqrt()    }}


Trait-Bounds

Um Vergleichs-, Additions- oder andere Operationen zu verwenden, muss angegeben werden, welche Traits der Typ implementiert. Kehren wir zur Funktion max zurück:

fn max<T: PartialOrd>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

Jetzt kompiliert der Code – wir haben gesagt, dass T partielle Ordnung (PartialOrd) unterstützen muss.

Traits können mit + kombiniert werden:

fn clone_and_compare<T: Clone + PartialOrd>(a: T, b: T) -> bool {    a.clone() > b.clone()}

Oder die where-Syntax für bessere Lesbarkeit verwenden:

fn some_function<T, U>(t: &T, u: &U) -> i32where    T:                

Blogs

Buchempfehlungen