Mehrfache Bounds
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 41 von 61.
Manchmal reicht eine einzelne Trait-Begrenzung nicht aus. Möglicherweise benötigen Sie einen generischen Typ, der sowohl ausgegeben werden kann als auch eine Zusammenfassung bereitstellt. Rust ermöglicht es Ihnen, mehrere Traits mithilfe der +-Syntax zu fordern.
So geben Sie an, dass ein Typ zwei Traits implementieren muss:
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
Die Einschränkung T: Display + Summary bedeutet: „T muss sowohl Display als auch Summary implementieren“. Innerhalb der Funktion kannst du Funktionen beider Traits nutzen – das Drucken mit {} (von Display) und das Aufrufen von summarize() (von Summary).
Sie können so viele Traits wie nötig verketten:
fn process<T: Display + Summary + Clone>(item: T) {
// Kann drucken, zusammenfassen UND klonen
}
Dieses Muster ist essenziell, wenn Ihre Funktion auf mehreren Verhaltensweisen basiert. Anstatt jeden beliebigen Typ zu akzeptieren und zu hoffen, dass es funktioniert, deklarieren Sie explizit genau, welche Fähigkeiten erforderlich sind – und der Compiler erzwingt dies zur Kompilierzeit.
Aufgabe
EinfachLassen Sie uns ein Produktinspektionssystem bauen, das erfordert, dass Artikel über mehrere Fähigkeiten verfügen! Sie werden eine generische Funktion erstellen, die nur Typen akzeptiert, die sowohl ein benutzerdefiniertes Trait als auch ein Standard-Trait implementieren, und so demonstrieren, wie die +-Syntax mehrere Bounds kombiniert.
Sie werden Ihren Code auf zwei Dateien aufteilen:
product.rs: Definieren Sie ein öffentlichesInspectable-Trait mit einer Methode namensinspect, die&selfentgegennimmt und einenStringmit Inspektionsdetails zurückgibt. Erstellen Sie dann eine öffentlicheGadget-Struktur mit den öffentlichen Feldernname(String) undserial(u32). Ihr Gadget sollte sowohlInspectable(Rückgabe von"Inspecting: {}") als auchstd::fmt::Display(Formatierung als"{} (SN: {})") implementieren. Erstellen Sie schließlich eine öffentliche generische Funktion namensfull_report, die jeden TypTakzeptiert, der sowohlDisplayals auchInspectableimplementiert. Diese Funktion sollte zwei Zeilen ausgeben: zuerst das Element unter Verwendung des{}-Formatierers, dann das Ergebnis des Aufrufs voninspect().main.rs: Binden Sie Ihr Produktmodul ein und erstellen Sie eineGadget-Instanz unter Verwendung der bereitgestellten Eingaben. Rufen Siefull_reportmit Ihrem Gadget auf, um zu zeigen, dass es beide Trait-Anforderungen erfüllt.
Die Stärke von multiplen Bounds liegt darin, dass Ihre full_report-Funktion Fähigkeiten aus beiden Traits nutzen kann – die ansprechende Anzeige des Elements UND das Abrufen von Inspektionsdetails – alles garantiert zur Kompilierzeit.
Ihre Ausgabe sollte sowohl das Anzeigeformat als auch das Inspektionsergebnis zeigen:
{name} (SN: {serial})
Inspecting: {name}Zum Beispiel mit den Eingaben Smartwatch und 98765:
Smartwatch (SN: 98765)
Inspecting: SmartwatchSie erhalten zwei Eingaben: den Namen des Gadgets und die Seriennummer (parsen als u32).
Spickzettel
Sie können mehrere Trait-Bounds für einen generischen Typ mit der Syntax + anfordern:
fn function_name<T: Trait1 + Trait2>(item: T) {
// Kann Fähigkeiten beider Traits nutzen
}
Beispiel mit Display und einem benutzerdefinierten Trait:
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
Sie können bei Bedarf mehrere Traits verketten:
fn process<T: Display + Summary + Clone>(item: T) {
// Kann drucken, zusammenfassen UND klonen
}
Der Compiler erzwingt zur Kompilierzeit, dass der Typ alle angegebenen Traits implementiert.
Probier es selbst
mod product;
use product::{Gadget, full_report};
fn main() {
// Eingaben lesen
let mut name = String::new();
std::io::stdin().read_line(&mut name).expect("Failed to read line");
let name = name.trim().to_string();
let mut serial_input = String::new();
std::io::stdin().read_line(&mut serial_input).expect("Failed to read line");
let serial: u32 = serial_input.trim().parse().expect("Failed to parse serial");
// TODO: Erstelle eine Gadget-Instanz mit dem Namen und der Seriennummer
// TODO: Rufe full_report mit deinem Gadget auf
}
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