Anhang C: Ableitbare Trait
An verschiedenen Stellen im Buch haben wir das Attribut derive besprochen, das
du auf eine Struktur- oder Aufzählungsdefinition anwenden kannst. Das Attribut
derive generiert Code, der ein Trait (Merkmal) mit seiner eigenen
Standard-Implementierung auf dem Typ implementiert, den du mit der
derive-Syntax annotiert hast.
In diesem Anhang findest du eine Referenz aller Traits in der
Standardbibliothek, die du mit derive verwenden kannst. Jeder Abschnitt
umfasst:
- Welche Operatoren und Methoden nach dem Ableiten dieses Traits ermöglicht werden
- Was die Implementierung des durch
derivebereitgestellten Traits bewirkt - Was die Implementierung des Traits über den Typ aussagt
- Die Bedingungen, unter denen du das Trait implementieren darfst oder nicht
- Beispiele für Operationen, die dieses Trait erfordern
Wenn du ein anderes Verhalten wünschst als das, das durch das Attribut derive
bereitgestellt wird, schaue in die Standardbibliotheksdokumentation
zu den Traits, um zu erfahren, wie sie manuell implementiert werden können.
Die hier aufgelisteten Traits sind die einzigen, die von der Standardbibliothek
definiert werden und die mit derive in deinen Typen implementiert werden
können. Andere in der Standardbibliothek definierte Traits haben kein sinnvolles
Standardverhalten, sodass es an dir liegt, sie so zu implementieren, wie es für
dein Vorhaben sinnvoll ist.
Ein Beispiel für ein Trait, das nicht abgeleitet werden kann, ist Display, das
die Formatierung für Endbenutzer übernimmt. Du solltest immer eine geeignete Art
und Weise in Betracht ziehen, einen Typ für einen Endbenutzer anzuzeigen. Welche
Teile des Typs sollte ein Endbenutzer sehen dürfen? Welche Teile würden sie für
relevant halten? Welches Datenformat wäre für sie am relevantesten? Der
Rust-Compiler verfügt nicht über dieses Wissen, sodass er kein angemessenes
Standardverhalten für dich bereitstellen kann.
Die Liste der ableitbaren Traits in diesem Anhang ist nicht vollständig:
Bibliotheken können derive für ihre eigenen Traits implementieren, sodass die
Liste der Traits, die du mit derive verwenden kannst, wahrlich unbegrenzt ist.
Das Implementieren von derive verwendet ein prozedurales Makro, das im
Abschnitt „Benutzerdefinierte derive-Makros“ in
Kapitel 20 behandelt wird.
Debug für die Programmierer-Ausgabe
Das Trait Debug ermöglicht das Debuggen von Formatierungen in
Formatierungs-Strings, die du durch :? innerhalb von Platzhaltern {}
angibst.
Das Trait Debug erlaubt es dir, Instanzen eines Typs zu Debugging-Zwecken
auszugeben, sodass du und andere Programmierer, die deinen Typ verwenden, eine
Instanz zu einem bestimmten Zeitpunkt der Programmausführung untersuchen können.
Das Trait Debug ist beispielsweise beim Verwenden des Makros assert_eq!
erforderlich. Dieses Makro gibt die Werte der Instanzen, die als Argumente
angegeben wurden, aus, wenn die Gleichheitszusicherung fehlschlägt, damit
Programmierer sehen können, warum die beiden Instanzen nicht gleich waren.
PartialEq und Eq für Gleichheitsvergleiche
Das Trait PartialEq erlaubt dir, Instanzen eines Typs auf Gleichheit zu prüfen
und ermöglicht das Verwenden der Operatoren == und !=.
Das Ableiten von PartialEq implementiert die Methode eq. Wenn PartialEq
für Strukturen abgeleitet wird, sind zwei Instanzen nur dann gleich, wenn
alle Felder gleich sind, und die Instanzen sind nicht gleich, wenn wenigstens
ein Feld nicht gleich ist. Beim Ableiten für Aufzählungen ist jede Variante
gleich sich selbst und nicht gleich den anderen Varianten.
Das Trait PartialEq ist beispielsweise beim Verwenden des Makros assert_eq!
erforderlich, das in der Lage sein muss, zwei Instanzen eines Typs auf
Gleichheit zu prüfen.
Das Trait Eq hat keine Methoden. Sein Zweck ist es, zu signalisieren, dass für
jeden Wert des annotierten Typs der Wert gleich sich selbst ist. Das Trait Eq
kann nur auf Typen angewandt werden, die auch PartialEq implementieren, obwohl
nicht alle Typen, die PartialEq implementieren, Eq implementieren können.
Ein Beispiel dafür sind Fließkomma-Zahlentypen: Die Implementierung von
Fließkomma-Zahlen besagt, dass zwei Instanzen des Wertes NaN nicht
vergleichbar sind.
Ein Beispiel dafür, wann Eq erforderlich ist, ist für Schlüssel in einer
HashMap<K, V>, damit HashMap<K, V> erkennen kann, ob zwei Schlüssel gleich
sind.
PartialOrd und Ord für Sortiervergleiche
Das Trait PartialOrd erlaubt dir, Instanzen eines Typs zum Sortieren zu
vergleichen. Ein Typ, der PartialOrd implementiert, kann mit den Operatoren
<, >, <= und >= verwendet werden. Du kannst das Trait PartialOrd nur
auf Typen anwenden, die auch PartialEq implementieren.
Das Ableiten von PartialOrd implementiert die Methode partial_cmp, die eine
Option<Ordering> zurückgibt, die None ist, wenn die angegebenen Werte nicht
vergleichbar sind. Ein Beispiel für einen Wert, der nicht vergleichbar ist,
obwohl die meisten Werte dieses Typs verglichen werden können, ist die
Fließkommazahl NaN. Der Aufruf von partial_cmp mit einer beliebigen
Fließkommazahl und dem Fließkommawert NaN ergibt None.
Beim Ableiten auf Strukturen vergleicht PartialOrd zwei Instanzen, indem es
den Wert in jedem Feld in der Reihenfolge vergleicht, in der die Felder in der
Strukturdefinition erscheinen. Beim Ableiten auf Aufzählungen werden Varianten,
die in der Aufzählungsdefinition früher deklariert sind, als kleiner als die
später aufgeführten Varianten betrachtet.
Das Trait PartialOrd ist z.B. für die Methode gen_range aus der Crate rand
erforderlich, die einen Zufallswert aus einem Wertebereich erzeugt, der durch
einen Bereichsausdruck festgelegt wird.
Das Trait Ord erlaubt dir zu wissen, dass für zwei beliebige Werte des
annotierten Typs eine gültige Reihenfolge existiert. Das Trait Ord
implementiert die Methode cmp, die Ordering statt Option<Ordering>
zurückgibt, weil eine gültige Reihenfolge immer möglich sein wird. Du kannst das
Trait Ord nur auf Typen anwenden, die auch PartialOrd und Eq
implementieren (und Eq erfordert PartialEq). Beim Ableiten auf Strukturen
und Aufzählungen verhält sich cmp genauso wie die abgeleitete Implementierung
für partial_cmp mit PartialOrd.
Ein Beispiel dafür, wann Ord erforderlich ist, ist das Speichern von Werten
in einem BTreeSet<T>, einer Datenstruktur, die Daten auf Grundlage der
Sortierreihenfolge der Werte speichert.
Clone und Copy zum Duplizieren von Werten
Das Trait Clone erlaubt es dir, explizit eine tiefe Kopie eines Wertes zu
erstellen, und der Vervielfältigungsprozess könnte die Ausführung von beliebigem
Code und das Kopieren von Daten im Heap beinhalten. Siehe Abschnitt „Variablen
und Daten im Zusammenspiel mit Clone“
in Kapitel 4 für weitere Informationen zu Clone.
Das Ableiten von Clone implementiert die Methode clone, die, wenn sie für
den gesamten Typ implementiert ist, clone auf jedem der Teile des Typs
aufruft. Das bedeutet, dass alle Felder oder Werte des Typs auch Clone
implementieren müssen, um Clone abzuleiten.
Ein Beispiel dafür, wann Clone erforderlich ist, ist der Aufruf der Methode
to_vec auf einem Slice. Der Slice besitzt die Typ-Instanzen nicht, die er
enthält, aber der von to_vec zurückgegebene Vektor muss seine Instanzen
besitzen, also ruft to_vec bei jedem Element clone auf. Daher muss der im
Slice gespeicherte Typ Clone implementieren.
Das Trait Copy erlaubt es dir, einen Wert zu duplizieren, indem nur die auf
dem Stack gespeicherten Bits kopiert werden; es ist kein spezieller Code
notwendig. Weitere Informationen zu Copy findest du im Abschnitt „Reine
Stack-Daten: Copy“ in Kapitel 4.
Das Trait Copy definiert keine Methoden, um Programmierer daran zu hindern,
diese Methoden zu überladen und die Annahme zu verletzen, dass kein spezieller
Code ausgeführt wird. Auf diese Weise können alle Programmierer davon ausgehen,
dass das Kopieren eines Wertes sehr schnell gehen wird.
Du kannst Copy auf jeden Typ ableiten, dessen Teile alle Copy
implementieren. Du kannst das Trait Copy nur auf Typen anwenden, die auch
Clone implementieren, weil ein Typ, der Copy implementiert, eine triviale
Implementierung von Clone hat, die die gleiche Aufgabe wie Copy erfüllt.
Das Trait Copy ist selten erforderlich; Typen, die Copy implementieren,
verfügen über Optimierungen, d.h. du musst nicht clone aufrufen, was den Code
prägnanter macht.
Alles, was mit Copy möglich ist, kannst du auch mit Clone erreichen, aber
der Code könnte langsamer sein oder an manchen Stellen clone erforderlich
machen.
Hash für die Abbildung eines Wertes auf einen Wert fester Größe
Das Trait Hash erlaubt es dir, eine Instanz eines Typs beliebiger Größe zu
nehmen und diese Instanz mithilfe einer Hash-Funktion auf einen Wert fester
Größe abzubilden. Das Ableiten von Hash implementiert die Methode hash. Die
abgeleitete Implementierung der Methode hash kombiniert das Ergebnis des
Aufrufs von hash für alle Teile des Typs, d.h. alle Felder oder Werte müssen
ebenfalls Hash implementieren, um Hash abzuleiten.
Ein Beispiel dafür, wann Hash erforderlich ist, ist das Speichern von
Schlüsseln in einer HashMap<K, V>, um Daten effizient zu speichern.
Default für Standardwerte
Das Trait Default erlaubt es dir, einen Standardwert für einen Typ zu
definieren. Das Ableiten von Default implementiert die Funktion default. Die
abgeleitete Implementierung der Funktion default ruft die Funktion default
für jeden Teil des Typs auf, d.h. alle Felder oder Werte in dem Typ müssen auch
Default implementieren, um Default abzuleiten.
Die Funktion Default::default wird häufig in Kombination mit der Syntax zur
Aktualisierung von Strukturen verwendet, die im Abschnitt „Instanzen erzeugen
mit der Strukturaktualisierungssyntax“ in Kapitel 5
besprochen wird. Du kannst einige Felder einer Struktur anpassen und dann einen
Standardwert für den Rest der Felder festlegen und verwenden, indem du
...Default::default() schreibst.
Das Trait Default ist erforderlich, wenn du die Methode unwrap_or_default
z.B. auf Instanzen von Option<T> verwendest. Wenn die Option<T> den Wert
None hat, gibt die Methode unwrap_or_default das Ergebnis von
Default::default für den Typ T zurück, der in Option<T> gespeichert ist.