Datenstrukturen mit Generics wiederverwenden

RustBeginner
Jetzt üben

Einführung

Konkrete Typen sind nützlich, wenn Daten immer dieselbe feste Struktur haben. Wenn Sie jedoch für Zahlen und Text nahezu identische Typen wiederholen, wird ein Programm schwieriger zu warten. Sie haben bereits standardmäßige generische Typen wie Option<u32>, Vec<String>, HashMap<String, u32> und Result<T, E> verwendet. In diesem Lab definieren Sie selbst einen generischen Typ. Mit Rust-Generics kann eine Definition mit mehreren konkreten Typen arbeiten, während der Compiler weiterhin jede Verwendung prüft.

In diesem Lab machen Sie die vorbereitete Struktur Pair generisch und verallgemeinern anschließend eine kleine Hilfsfunktion, die geliehene Werte verwendet. Das längere Programm und die Beispieldaten sind bereits vorhanden. Ihre Aufgabe konzentriert sich daher auf die wenigen Typangaben, die eine Wiederverwendung ermöglichen.

Die Struktur Pair generisch machen

In diesem Schritt ersetzen Sie den festen Typ i32 der Struktur durch einen Typparameter. Dadurch kann dieselbe Definition entweder Punktzahlen oder Routennamen aufnehmen.

Wechseln Sie in das vorbereitete Cargo-Paket:

cd /home/labex/project/pair-demo

Sehen Sie sich die kurze Quelldatei an. Der Befehl sed -n gibt den angeforderten Zeilenbereich aus, ohne die Datei zu ändern:

sed -n '1,120p' src/main.rs

Der Wert scores enthält Ganzzahlen, während routes eigene String-Werte enthält. Die aktuelle Definition von Pair legt beide Felder auf i32 fest und kann daher nicht beide Werte darstellen. Das vorbereitete Attribut #[derive(Debug)] würde eine Debug-Ausgabe für den Typ erzeugen. Das sichtbare Programm verwendet diese Ausgabe jedoch nicht; sie gehört nicht zur Aufgabe, Pair generisch zu machen.

Ein generischer Typ führt innerhalb spitzer Klammern einen Platzhalter wie T ein. Jedes erzeugte Pair hat weiterhin einen konkreten Typ: Der Compiler leitet für scores Pair<i32> und für routes Pair<String> ab.

Öffnen Sie die Quelldatei in Nano:

nano src/main.rs

Nehmen Sie am Anfang der Datei diese drei kleinen Änderungen vor:

  • Ändern Sie struct Pair { in struct Pair<T> {.
  • Ändern Sie den Typ von left von i32 in T.
  • Ändern Sie den Typ von right von i32 in T.

Die fertige Struktur lautet:

struct Pair<T> {
    left: T,
    right: T,
}

Beide Felder verwenden hier dasselbe T. Dadurch kann ein Paar nicht versehentlich Felder mit nicht zusammenpassenden Typen enthalten. Speichern Sie mit Strg+O, drücken Sie Enter und beenden Sie Nano mit Strg+X.

Lassen Sie Cargo das Paket auf Typfehler prüfen:

cargo check

Eine Zeile, die mit Finished endet, bedeutet, dass der Compiler sowohl Pair<i32> als auch Pair<String> akzeptiert hat. Führen Sie nun das Programm aus:

cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82

Die ersten beiden Zeilen zeigen, dass eine einzige Strukturdefinition nun zwei verschiedene konkrete Typen speichern kann. Die vorhandene Hilfsfunktion first akzeptiert weiterhin nur Pair<i32>. Im nächsten Schritt machen Sie auch diese Funktion generisch.

Eine Funktion mit geliehenen Werten verallgemeinern

In diesem Schritt geben Sie der Funktion first einen eigenen Typparameter. Dadurch kann sie jedes Pair<T> ausleihen und eine Referenz auf dessen ersten Wert zurückgeben.

Sehen Sie sich die aktuelle Hilfsfunktion am Anfang der Datei an:

sed -n '1,30p' src/main.rs

Der Eingabetyp Pair<i32> und der Ausgabetyp &i32 machen die Funktion derzeit auf Punktzahlen beschränkt. Eine generische Funktion deklariert <T> nach ihrem Namen und verwendet dasselbe T überall dort, wo die zugehörigen Eingabe- und Ausgabetypen vorkommen.

Öffnen Sie die Datei:

nano src/main.rs

Ändern Sie ausschließlich die Funktionsdeklaration von:

fn first(pair: &Pair<i32>) -> &i32 {

in:

fn first<T>(pair: &Pair<T>) -> &T {

Die Funktion leiht sich das Paar aus und gibt ein geliehenes Feld zurück. Sie verschiebt oder kopiert den gespeicherten Wert daher nicht. Rust kann T aus jedem Argument ableiten; Aufrufer müssen den Typ nicht ausdrücklich angeben.

Ersetzen Sie anschließend // FIRST_ROUTE_REPORT durch einen Aufruf, der zeigt, dass die Hilfsfunktion auch mit Text funktioniert:

    println!("first route: {}", first(&routes));

Speichern Sie die Datei und beenden Sie Nano. Prüfen und starten Sie anschließend das fertige Programm:

cargo check
cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82
first route: north

Die beiden Zeilen mit first stammen aus derselben generischen Funktion. Rust leitet beim Aufruf für die Punktzahl T = i32 und beim Aufruf für die Route T = String ab. Anschließend prüft Rust jeden Aufruf mit dem jeweils konkreten Typ. Die Wiederverwendung setzt die statische Typprüfung nicht außer Kraft.

Zusammenfassung

Sie haben ein konkretes Paar durch Pair<T> ersetzt, es sowohl mit Ganzzahlen als auch mit Zeichenketten verwendet und eine Hilfsfunktion mit geliehenen Werten verallgemeinert, ohne Traits einzuführen oder Implementierungen zu duplizieren.