Generische Funktionen
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 27 von 61.
Generics sind nicht auf Structs beschränkt – Sie können auch eigenständige Funktionen schreiben, die mit jedem Typ funktionieren. Dies ist nützlich, wenn Sie eine Hilfsfunktion benötigen, die nicht zu einem bestimmten Struct gehört, aber dennoch flexibel sein soll.
Die Syntax entspricht dem, was du bereits bei Structs gesehen hast. Deklariere den generischen Parameter in spitzen Klammern nach dem Funktionsnamen und verwende ihn dann in den Parametern und im Rückgabetyp:
fn identity<T>(value: T) -> T {
value
}
Diese identity-Funktion nimmt einen Wert eines beliebigen Typs entgegen und gibt ihn unverändert zurück. Das <T> deklariert den Generic, value: T akzeptiert ihn als Parameter und -> T gibt den Rückgabetyp an. Wenn Sie die Funktion aufrufen, leitet Rust den konkreten Typ ab:
let num = identity(42); // T ist i32
let text = identity("hello"); // T ist &str
Sie können auch mehrere generische Parameter in Funktionen verwenden, genau wie bei Structs:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // gibt (i32, &str) zurück
Generische Funktionen ermöglichen es Ihnen, wiederverwendbare Logik einmal zu schreiben und auf viele Typen anzuwenden, wodurch Code-Duplizierung reduziert und gleichzeitig die Typsicherheit gewahrt bleibt.
Aufgabe
EinfachLassen Sie uns ein Utility-Modul mit generischen Funktionen erstellen, die mit jedem Typ funktionieren können! Sie werden eigenständige Funktionen erstellen, die zeigen, wie Generics Ihren Code flexibel und wiederverwendbar machen, ohne an eine bestimmte Struktur gebunden zu sein.
Sie werden Ihren Code auf zwei Dateien aufteilen:
utils.rs: Erstellen Sie eine Sammlung öffentlicher generischer Utility-Funktionen:wrap_in_pair<T>— nimmt einen einzelnen Wert entgegen und gibt ein Tupel zurück, das diesen Wert zweimal enthält:(value, value). Dies erfordert, dass der TypCloneimplementiert, verwenden Sie also<T: Clone>swap<T, U>— nimmt zwei Werte potenziell unterschiedlicher Typen entgegen und gibt sie in umgekehrter Reihenfolge als Tupel(U, T)zurück
main.rs: Importieren Sie Ihr Utility-Modul und demonstrieren Sie diese generischen Funktionen mit verschiedenen Typen. Zeigen Sie, wie dieselben Funktionsdefinitionen nahtlos mit Ganzzahlen, Fließkommazahlen und Strings umgehen.
Demonstrieren Sie in Ihrer Hauptdatei Ihre Utility-Funktionen, indem Sie:
wrap_in_pairmit einer Ganzzahl (erste Eingabe, alsi32geparst) verwenden und beide Elemente ausgebenwrap_in_pairmit einem String (zweite Eingabe) verwenden und beide Elemente ausgebenswapmit einer Ganzzahl (dritte Eingabe, alsi32geparst) und einem String (vierte Eingabe) verwenden und dann das vertauschte Ergebnis ausgeben
Ihre Ausgabe sollte folgendem Format entsprechen:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Zum Beispiel mit den Eingaben 5, hello, 42 und world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Beachten Sie, wie wrap_in_pair für Ganzzahlen und Strings identisch funktioniert und swap zwei völlig unterschiedliche Typen verarbeitet – das ist die Flexibilität generischer Funktionen!
Sie erhalten vier Eingaben: eine Ganzzahl, einen String, eine weitere Ganzzahl und einen weiteren String.
Spickzettel
Generische Funktionen ermöglichen es Ihnen, eigenständige Hilfsfunktionen zu schreiben, die mit jedem Typ funktionieren. Deklarieren Sie den generischen Parameter in spitzen Klammern nach dem Funktionsnamen:
fn identity<T>(value: T) -> T {
value
}
Das <T> deklariert den generischen Typparameter, der dann in Funktionsparametern und Rückgabetypen verwendet werden kann. Rust leitet den konkreten Typ ab, wenn Sie die Funktion aufrufen:
let num = identity(42); // T ist i32
let text = identity("hello"); // T ist &str
Sie können mehrere generische Parameter in einer einzigen Funktion verwenden:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // gibt (i32, &str) zurück
Wenn ein generischer Typ bestimmte Fähigkeiten benötigt, verwenden Sie Trait-Bounds:
fn duplicate<T: Clone>(value: T) -> (T, T) {
(value.clone(), value)
}
Probier es selbst
mod utils;
use std::io;
fn main() {
// Lies die vier Eingaben ein
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Verwende wrap_in_pair mit num1 und gib das Ergebnis aus
// Format: Pair of ints: ({value}, {value})
// TODO: Verwende wrap_in_pair mit str1 und gib das Ergebnis aus
// Format: Pair of strings: ({value}, {value})
// TODO: Verwende swap mit num2 und str2, und gib dann das vertauschte Ergebnis aus
// Format: Swapped: ({string}, {int})
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Methoden und Verhalten
Einführung in ImplementierungsblöckeDer Self-ParameterVeränderliche MethodenAssoziierte FunktionenMehrere ImplementierungsblöckeMethod ChainingRückblick – Rechteck-Aktionen4Projekt: Virtuelles Haustier
Das Haustier definierenDas Haustier füttern7Standard-Traits
Das Debug-TraitDas Display-TraitClone und CopyGleichheits-TraitsZusammenfassung – Druckbarer Punkt10Projekt: Dokumentensystem
Das Draw-TraitText-Komponente2Kapselung und Module
Grundlagen der ModuleDas Public-SchlüsselwortPrivate FelderGetterSetterWiederholung – Sicherer Tresor5Generics
Generische StructsGenerische MethodenMehrere generische TypenGenerische FunktionenZusammenfassung - Koordinatenpunkt8Traits als Einschränkungen
Syntax für Trait BoundsMehrfache BoundsDie Where-KlauselTypen mit Traits zurückgebenRückblick – Generischer Drucker11Design Patterns in Rust
Newtype-PatternKompositionDas Drop-TraitFrom und IntoZusammenfassung – Smart Pointer Mock3Fortgeschrittene Enums
Enums mit DatenMethoden für EnumsMatching von DatenvariantenDas Option-Enum erneut betrachtetZusammenfassung – Shape-Enum6Definition von Traits
Was ist ein Trait?Traits implementierenStandardimplementierungenStandardimplementierungen überschreibenTraits mit ParameternZusammenfassung – Media Player