Generische Structs
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 24 von 61.
Bisher hatte jedes Struct, das Sie erstellt haben, Felder mit spezifischen, konkreten Typen. Ein Pet hat einen String-Namen, ein Rectangle hat f64-Dimensionen. Aber was ist, wenn Sie ein Struct erstellen möchten, das jeden Datentyp enthalten kann?
Hier kommen Generics ins Spiel. Generics ermöglichen es Ihnen, flexiblen, wiederverwendbaren Code zu schreiben, der mit mehreren Typen funktioniert. Anstatt einen spezifischen Typ fest zu kodieren, verwenden Sie einen Platzhalter – normalerweise T –, der durch einen konkreten Typ ersetzt wird, wenn Sie das Struct verwenden.
Hier ist ein einfaches Wrapper-Struct, das jeden Typ aufnehmen kann:
struct Wrapper<T> {
value: T,
}
Das <T> nach dem Struct-Namen deklariert einen generischen Typparameter. Innerhalb des Structs fungiert T als Platzhalter für den Typ, den Sie tatsächlich verwenden werden. Wenn Sie eine Instanz erstellen, leitet Rust den konkreten Typ ab:
let int_wrapper = Wrapper { value: 42 }; // T ist i32
let float_wrapper = Wrapper { value: 3.14 }; // T ist f64
let text_wrapper = Wrapper { value: "hello" }; // T ist &str
Jeder dieser Typen ist ein anderer konkreter Typ — Wrapper<i32>, Wrapper<f64> und Wrapper<&str> — aber sie alle teilen sich dieselbe Struct-Definition. Dies macht es unnötig, separate IntWrapper, FloatWrapper und StringWrapper Structs zu schreiben, die im Wesentlichen dasselbe tun.
Der Buchstabe T ist nur eine Konvention (kurz für „Type“). Man könnte jeden gültigen Bezeichner verwenden, aber T ist in Rust und den meisten anderen Sprachen mit Generics Standard.
Aufgabe
EinfachLassen Sie uns einen generischen Container bauen, der jeden Werttyp aufnehmen kann! Sie werden eine Container-Struktur erstellen, die zeigt, wie Generics Ihren Code flexibel und wiederverwendbar machen.
Sie werden Ihren Code über zwei Dateien organisieren:
container.rs: Definieren Sie eine öffentliche generische Struktur namensContainer<T>mit einem einzigen öffentlichen Felditemvom TypT. Dieser Container sollte in der Lage sein, jeden Typ aufzunehmen – Ganzzahlen, Fließkommazahlen, Strings oder alles andere.main.rs: Binden Sie Ihr Container-Modul ein und demonstrieren Sie dessen Flexibilität, indem Sie Container erstellen, die verschiedene Datentypen enthalten. Sie werden drei Container erstellen und anzeigen, was jeder einzelne enthält.
Erstellen Sie in Ihrer Hauptdatei die folgenden Container:
- Einen Container, der einen Ganzzahlwert enthält (die erste Eingabe, geparst als
i32) - Einen Container, der einen Fließkommawert enthält (die zweite Eingabe, geparst als
f64) - Einen Container, der einen String enthält (die dritte Eingabe, beibehalten als
String)
Ihre Ausgabe sollte den Inhalt jedes Containers in diesem Format anzeigen:
Integer container: {value}
Float container: {value}
String container: {value}Zum Beispiel mit den Eingaben 42, 3.14 und hello:
Integer container: 42
Float container: 3.14
String container: helloBeachten Sie, wie dieselbe Container<T>-Definition für alle drei verschiedenen Typen funktioniert – das ist die Macht der Generics!
Sie erhalten drei Eingaben: einen Ganzzahlwert, einen Fließkommawert und einen String-Wert.
Spickzettel
Generics ermöglichen es Ihnen, Structs zu erstellen, die mithilfe eines Typparameter-Platzhalters beliebige Datentypen enthalten können.
Um ein generisches Struct zu deklarieren, verwenden Sie spitze Klammern mit einem Typparameter (üblicherweise T):
struct Wrapper<T> {
value: T,
}
Das <T> deklariert einen generischen Typparameter, und T innerhalb des Structs fungiert als Platzhalter für den tatsächlichen Typ.
Beim Erstellen von Instanzen leitet Rust den konkreten Typ ab:
let int_wrapper = Wrapper { value: 42 }; // T ist i32
let float_wrapper = Wrapper { value: 3.14 }; // T ist f64
let text_wrapper = Wrapper { value: "hello" }; // T ist &str
Jede Instanz wird zu einem anderen konkreten Typ (Wrapper<i32>, Wrapper<f64>, Wrapper<&str>), während sie dieselbe Struct-Definition teilen.
Probier es selbst
mod container;
use container::Container;
fn main() {
// Die drei Eingaben lesen
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Drei Container mit Container<T> erstellen
// 1. Ein Integer-Container, der int_value enthält
// 2. Ein Float-Container, der float_value enthält
// 3. Ein String-Container, der string_value enthält
// TODO: Den Inhalt jedes Containers im erforderlichen Format ausgeben
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
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