Anhang C: Ableitbare Merkmale (traits)

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 Merkmal (trait) 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 Merkmale in der Standardbibliothek, die du mit derive verwenden kannst. Jeder Abschnitt umfasst:

  • Welche Operatoren und Methoden nach Ableiten dieses Merkmals ermöglicht werden
  • Was die Implementierung des durch derive bereitgestellten Merkmals bewirkt
  • Was die Implementierung des Merkmals über den Typ aussagt
  • Die Bedingungen, unter denen du das Merkmal implementieren darfst oder nicht
  • Beispiele für Operationen, die dieses Merkmal erfordern

Wenn du ein anderes Verhalten wünschst als das, das durch das Attribut derive bereitgestellt wird, schaue in die Standardbibliotheksdokumentation zu den Merkmalen, um zu erfahren, wie sie manuell implementiert werden können.

Diese hier aufgelisteten Merkmale 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 Merkmale 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 Merkmal, 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 Merkmale in diesem Anhang ist nicht vollständig: Bibliotheken können derive für ihre eigenen Merkmale implementieren, sodass die Liste der Merkmale, die du mit derive verwenden kannst, wahrlich unbegrenzt ist. Das Implementieren von derive verwendet ein prozedurales Makro, das im Abschnitt „Makros“ in Kapitel 19 behandelt wird.

Debug für die Programmierer-Ausgabe

Das Merkmal Debug ermöglicht das Debuggen von Formatierungen in Formatierungszeichenketten, die du durch Angeben von :? innerhalb Platzhalter {} angibst.

Das Merkmal 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 Merkmal 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 Merkmal 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 Merkmal 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 Merkmal 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 Merkmal 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 Keine-Zahl-Wertes (NaN) nicht gleichwertig 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 Merkmal 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 Merkmal 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 Merkmal PartialOrd ist z.B. für die Methode gen_range aus der Kiste rand erforderlich, die einen Zufallswert aus einem Wertebereich erzeugt, der durch einen Bereichsausdruck festgelegt wird.

Das Merkmal Ord erlaubt dir zu wissen, dass für zwei beliebige Werte des annotierten Typs eine gültige Reihenfolge existiert. Das Merkmal 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 Merkmal 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 Merkmal 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 Haldenspeicher beinhalten. Siehe den 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 Anteilstyp. Der Anteilstyp 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 Anteilstyp gespeicherte Typ Clone implementieren.

Das Merkmal Copy erlaubt es dir, einen Wert zu duplizieren, indem nur die auf dem Stapelspeicher gespeicherten Bits kopiert werden; es ist kein spezieller Code notwendig. Weitere Informationen zu Copy findest du im Abschnitt „Nur Stapelspeicher-Daten: Kopieren (copy)“ in Kapitel 4.

Das Merkmal 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 Merkmal Copy nur auf Typen anwenden, die auch Clone implementieren, weil ein Typ, der Copy implementiert, eine triviale Implementierung von Clone hat, das die gleiche Aufgabe wie Copy erfüllt.

Das Merkmal Copy ist selten erforderlich; Typen, die Copy implementieren, verfügen über Optimierungen, d.h. du mussst 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 Merkmal 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 Merkmal 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 aus anderen 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 Merkmal 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.