Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Gemeinsames Verhalten definieren mit Traits

Ein Trait (Merkmal) definiert die Funktionalität, die ein bestimmter Typ hat und mit anderen Typen teilen kann. Wir können Traits verwenden, um gemeinsames Verhalten auf abstrakte Weise zu definieren. Wir können Trait Bounds verwenden, um anzugeben, dass ein generischer Typ jeder Typ sein kann, der ein bestimmtes Verhalten aufweist.

Anmerkung: Traits sind einer Funktionalität recht ähnlich, die in anderen Sprachen oft Schnittstelle (interface) genannt wird, wenn auch mit einigen Unterschieden.

Ein Trait definieren

Das Verhalten eines Typs besteht aus den Methoden, die wir auf diesen Typ anwenden können. Verschiedene Typen haben das gleiche Verhalten, wenn wir bei allen die gleichen Methoden aufrufen können. Trait-Definitionen sind eine Möglichkeit, Methodensignaturen zu gruppieren, um eine Reihe von Verhaltensweisen zu definieren, die zum Erreichen eines bestimmten Zwecks erforderlich sind.

Nehmen wir zum Beispiel an, wir haben mehrere Strukturen (structs), die verschiedene Arten und Mengen von Text enthalten: Eine Struktur NewsArticle, die eine Nachricht enthält, die sich auf einen bestimmten Ort bezieht, und ein SocialPost, der maximal 280 Zeichen umfassen kann, sowie Metadaten, die angeben, ob es sich um eine neue Nachricht, eine Wiederholung oder eine Antwort auf eine andere Nachricht handelt.

Wir wollen eine Medienaggregator-Bibliotheks-Crate namens aggregator erstellen, die Zusammenfassungen von Daten anzeigen kann, die in einer NewsArticle- oder SocialPost-Instanz gespeichert sein könnten. Dazu brauchen wir eine Zusammenfassung von jedem Typ, und wir werden diese Zusammenfassung anfordern, indem wir eine Methode summarize auf einer Instanz aufrufen. Listing 10-12 zeigt die Definition eines öffentlichen Traits Summary, die dieses Verhalten zum Ausdruck bringt.

Dateiname: src/lib.rs

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}
}

Listing 10-12: Ein Trait Summary, dessen Verhalten aus der Methode summarize besteht

Hier deklarieren wir ein Trait mit dem Schlüsselwort trait und dann den Namen des Traits, der in diesem Fall Summary lautet. Wir deklarieren das Trait auch als pub, sodass Crates, die von dieser Crate abhängen, dieses Trait ebenfalls nutzen können, wie wir in einigen Beispielen sehen werden. Innerhalb der geschweiften Klammern deklarieren wir die Methodensignaturen, die das Verhalten der Typen beschreiben, die dieses Trait implementieren, was in diesem Fall fn summarize(&self) -> String ist.

Nach der Methodensignatur verwenden wir statt einer Implementierung in geschweiften Klammern ein Semikolon. Jeder Typ, der dieses Trait implementiert, muss sein eigenes benutzerdefiniertes Verhalten für den Methodenrumpf bereitstellen. Der Compiler wird sicherstellen, dass jeder Typ, der das Trait Summary hat, die Methode summarize mit genau dieser Signatur hat.

Ein Trait kann mehrere Methoden umfassen: Die Methodensignaturen werden zeilenweise aufgelistet und jede Zeile endet mit einem Semikolon.

Ein Trait für einen Typ implementieren

Nachdem wir nun die gewünschten Signaturen der Methoden des Traits Summary definiert haben, können wir sie für die Typen in unserem Medienaggregator implementieren. Listing 10-13 zeigt eine Implementierung des Traits Summary für die Struktur NewsArticle, die die Überschrift, den Autor und den Ort verwendet, um den Rückgabewert von summarize zu erzeugen. Für die Struktur SocialPost definieren wir summarize als den Benutzernamen, gefolgt vom gesamten Text der Nachricht, wobei wir davon ausgehen, dass der Inhalt der Nachricht bereits auf 280 Zeichen begrenzt ist.

Dateiname: src/lib.rs

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, von {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
}

Listing 10-13: Implementierung des Traits Summary für die Typen NewsArticle und SocialPost

Die Implementierung eines Traits für einen Typ ist ähnlich zur Implementierung regulärer Methoden. Der Unterschied besteht darin, dass wir nach impl den Namen des Traits schreiben, das wir implementieren wollen und dann das Schlüsselwort for gefolgt vom Namen des Typs, für den wir das Trait implementieren wollen. Innerhalb des impl-Blocks geben wir die Methodensignaturen an, die das Trait vorgibt. Anstatt nach jeder Signatur ein Semikolon zu schreiben, verwenden wir geschweifte Klammern und füllen den Methodenrumpf mit dem spezifischen Verhalten, das die Methoden des Traits für den jeweiligen Typ haben sollen.

Da die Bibliothek nun das Trait Summary auf NewsArticle und SocialPost implementiert hat, können Benutzer der Crate die Trait-Methoden auf Instanzen von NewsArticle und SocialPost auf die gleiche Weise aufrufen, wie wir reguläre Methoden aufrufen. Der einzige Unterschied besteht darin, dass der Benutzer das Trait sowie die Typen in den Gültigkeitsbereich bringen muss, um die zusätzlichen Trait-Methoden zu erhalten. Hier ist ein Beispiel dafür, wie eine binäre Crate unsere Bibliotheks-Crate aggregator verwenden könnte:

use aggregator::{self, Summary, SocialPost};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from("natürlich, wie du wahrscheinlich schon weißt"),
        reply: false,
        repost: false,
    };

    println!("1 neue Nachricht: {}", post.summarize());
}

Dieser Code gibt 1 neue Nachricht: horse_ebooks: natürlich, wie du wahrscheinlich schon weißt aus.

Andere Crates, die von der Crate aggregator abhängen, können auch das Trait Summary in den Gültigkeitsbereich bringen, um Summary auf ihren eigenen Typen zu implementieren. Eine Einschränkung ist, dass wir ein Trait für einen Typ nur dann implementieren können, wenn entweder das Trait oder der Typ lokal in unserer Crate vorhanden ist. Zum Beispiel können wir Standardbibliotheks-Traits wie Display auf einem benutzerdefinierten Typ wie SocialPost als Teil unserer aggregator-Crate-Funktionalität implementieren, weil der Typ SocialPost lokal zu unserer Crate aggregator gehört. Wir können auch Summary auf Vec<T> in unserer Crate aggregator implementieren, weil das Trait Summary lokal zu unserer Crate aggregator gehört.

Aber wir können externe Traits nicht auf externe Typen anwenden. Zum Beispiel können wir das Trait Display auf Vec<T> in unserer Crate aggregator nicht implementieren, weil Display und Vec<T> in der Standardbibliothek definiert sind und nicht lokal zu unserer Crate aggregator gehören. Diese Beschränkung ist Teil einer Eigenschaft von Programmen namens Kohärenz (coherence), genauer gesagt der Waisenregel (orphan rule), die so genannt wird, weil der übergeordnete Typ nicht vorhanden ist. Diese Regel stellt sicher, dass der Code anderer Personen deinen Code nicht brechen kann und umgekehrt. Ohne diese Regel könnten zwei Crates dasselbe Trait für denselben Typ implementieren und Rust wüsste nicht, welche Implementierung es verwenden sollte.

Standard-Implementierungen verwenden

Manchmal ist es nützlich, ein Standardverhalten für einige oder alle Methoden eines Traits zu haben, anstatt Implementierungen für alle Methoden für jeden Typ zu verlangen. Wenn wir dann das Trait für einen bestimmten Typ implementieren, können wir das Standardverhalten jeder Methode beibehalten oder überschreiben.

In Listing 10-14 geben wir einen Standard-String für die Methode summarize des Traits Summary an, anstatt nur die Methodensignatur zu definieren, wie wir es in Listing 10-12 getan haben.

Dateiname: src/lib.rs

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Lies mehr ...)")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
}

Listing 10-14: Definieren eines Traits Summary mit einer Standard-Implementierung der Methode summarize

Um eine Standard-Implementierung zu verwenden, um Instanzen von NewsArticle zusammenzufassen, geben wir einen leeren impl-Block mit impl Summary for NewsArticle {} an.

Auch wenn wir die Methode summarize nicht mehr direkt für NewsArticle definieren, haben wir eine Standard-Implementierung bereitgestellt und festgelegt, dass NewsArticle das Trait Summary implementiert. Infolgedessen können wir immer noch die Methode summarize einer NewsArticle-Instanz aufrufen, etwa so:

use aggregator::{self, NewsArticle, Summary};

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins gewinnen die Stanley-Cup-Meisterschaft!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("Die Pittsburgh Penguins sind erneut die beste \
                               Eishockeymannschaft in der NHL.",
        ),
    };

    println!("Neuer Artikel verfügbar! {}", article.summarize());
}

Dieser Code gibt Neuer Artikel verfügbar! (Lies mehr ...) aus.

Das Erstellen einer Standard-Implementierung erfordert nicht, dass wir an der Implementierung von Summary für SocialPost in Listing 10-13 etwas ändern. Der Grund dafür ist, dass die Syntax für das Überschreiben einer Standard-Implementierung die gleiche ist wie die Syntax für die Implementierung einer Trait-Methode, die keine Standard-Implementierung hat.

Standard-Implementierungen können andere Methoden desselben Traits aufrufen, selbst wenn diese anderen Methoden keine Standard-Implementierung haben. Auf diese Weise kann ein Trait eine Menge nützlicher Funktionalität bereitstellen und von den Implementierern nur die Angabe eines kleinen Teils verlangen. Zum Beispiel könnten wir das Trait Summary so definieren, dass wir eine Methode summarize_author haben, deren Implementierung erforderlich ist, und dann eine Methode summarize definieren, die eine Standard-Implementierung hat und die Methode summarize_author aufruft:

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Lies mehr von {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}
}

Um diese Version von Summary zu verwenden, müssen wir summarize_author nur dann definieren, wenn wir das Trait für einen Typ implementieren:

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Lies mehr von {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}
}

Nachdem wir summarize_author definiert haben, können wir summarize auf Instanzen der SocialPost-Struktur aufrufen, und die Standard-Implementierung von summarize wird die Definition von summarize_author aufrufen, die wir bereitgestellt haben. Da wir summarize_author implementiert haben, hat uns das Trait Summary das Verhalten der Methode summarize mitgeliefert, ohne dass wir weiteren Code schreiben müssen. Das sieht dann so aus:

use aggregator::{self, Summary, SocialPost};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from("natürlich, wie du wahrscheinlich schon weißt"),
        reply: false,
        repost: false,
    };

    println!("1 neue Nachricht: {}", post.summarize());
}

Dieser Code gibt 1 neue Nachricht: (Lies mehr von @horse_ebooks...) aus.

Beachte, dass es nicht möglich ist, die Standardimplementierung von einer übergeordneten Implementierung derselben Methode aus aufzurufen.

Traits als Parameter verwenden

Da du jetzt weißt, wie man Traits definiert und implementiert, können wir untersuchen, wie man Traits zur Definition von Funktionen verwendet, die viele verschiedene Typen akzeptieren. Wir verwenden das Trait Summary, das wir für die Typen NewsArticle und SocialPost in Listing 10-13 implementiert haben, um eine Funktion notify zu definieren, die die Methode summarize für ihren Parameter item aufruft, der von einem Typ ist, der das Trait Summary implementiert. Um dies zu tun, können wir die Syntax impl Trait verwenden, etwa so:

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, von {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

pub fn notify(item: &impl Summary) {
    println!("Eilmeldung! {}", item.summarize());
}
}

Anstelle eines konkreten Typs für den Parameter item geben wir das Schlüsselwort impl und den Trait-Namen an. Dieser Parameter akzeptiert jeden Typ, der das angegebene Trait implementiert. Im Rumpf von notify können wir alle Methoden von item aufrufen, die vom Trait Summary herrühren, zum Beispiel summarize. Wir können notify aufrufen und jede Instanz von NewsArticle und SocialPost angeben. Code, der die Funktion mit einem anderen Typ aufruft, z.B. String oder i32, lässt sich nicht kompilieren, da diese Typen kein Summary implementieren.

Trait Bound Syntax

Die Syntax impl Trait funktioniert für einfache Fälle, ist aber eigentlich syntaktischer Zucker für eine längere Form, die Trait Bound genannt wird; sie sieht so aus:

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}

pub fn notify<T: Summary>(item: &T) {
    println!("Eilmeldung! {}", item.summarize());
}
}

Diese längere Form entspricht dem Beispiel im vorigen Abschnitt, ist aber wortreicher. Wir platzieren Trait Bounds in der Deklaration des generischen Typparameters nach einem Doppelpunkt und innerhalb spitzer Klammern.

Die Syntax impl Trait ist bequem und ermöglicht in einfachen Fällen einen prägnanteren Code, während die umfassendere Trait Bound Syntax mehr Komplexität ausdrücken kann. Zum Beispiel können wir zwei Parameter haben, die Summary implementieren. Das Verwenden der Syntax impl Trait sieht folgendermaßen aus:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

Die Verwendung von impl Trait ist angemessen, wenn wir wollten, dass diese Funktion bei item1 und item2 unterschiedliche Typen haben kann (solange beide Typen Summary implementieren). Wenn beide Parameter aber den gleichen Typ haben sollen, müssen wir eine Trait Bound verwenden, so wie hier:

pub fn notify<T: Summary>(item1: &T, item2: &T) {

Der als Parametertyp für item1 und item2 angegebene generische Typ T schränkt die Funktion so ein, dass der konkrete Typ der als Argument für item1 und item2 übergebenen Werte derselbe sein muss.

Mehrere Trait Bounds mit der Syntax +

Wir können auch mehr als eine Trait Bound angeben. Nehmen wir an, wir wollen, dass sowohl notify als auch die Methode summarize die Bildschirmausgabe für item formatieren: Spezifizieren wir in der notify-Definition, dass item sowohl Display als auch Summary implementieren muss. Wir können dies mit der Syntax + tun:

pub fn notify(item: &(impl Summary + Display)) {

Die Syntax + ist auch bei Trait Bounds mit generischen Typen gültig:

pub fn notify<T: Summary + Display>(item: &T) {

Mit den beiden angegebenen Trait Bounds kann der Rumpf von notify die Methode summarize aufrufen und {} verwenden, um item zu formatieren.

Klarere Trait Bounds mit where-Klauseln

Zu viele Trait Bounds zu verwenden, hat seine Schattenseiten. Jeder generische Datentyp hat seine eigenen Trait Bounds, sodass Funktionen mit mehreren generischen Typparametern viele Trait Bound Angaben zwischen Funktionsname und Parameterliste enthalten können, wodurch die Funktionssignatur schwer lesbar wird. Aus diesem Grund hat Rust für die Angabe von Trait Bounds eine alternative Syntax in Form einer where-Klausel nach der Funktionssignatur. Anstatt das hier zu schreiben:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

können wir eine where-Klausel wie folgt verwenden:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    unimplemented!()
}

Die Signatur dieser Funktion ist übersichtlicher: Der Funktionsname, die Parameterliste und der Rückgabetyp liegen nahe beieinander, ähnlich wie bei einer Funktion ohne viele Trait Bounds.

Rückgabetypen, die Traits implementieren

Wir können die Syntax impl Trait auch für den Rückgabetyp verwenden, wie hier gezeigt:

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, von {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable() -> impl Summary {
    SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from("natürlich, wie du wahrscheinlich schon weißt"),
        reply: false,
        repost: false,
    }
}
}

Durch Verwenden von impl Summary für den Rückgabetyp legen wir fest, dass die Funktion returns_summarizable einen Typ zurückgibt, der das Trait Summary implementiert, ohne den konkreten Typ zu nennen. In diesem Fall gibt returns_summarizable einen SocialPost zurück, aber der Code, der diese Funktion aufruft, muss das nicht wissen.

Die Fähigkeit, einen Rückgabetyp nur durch das Trait, das er implementiert, zu spezifizieren, ist besonders nützlich im Zusammenhang mit Closures und Iteratoren, die wir in Kapitel 13 behandeln. Closures und Iteratoren erzeugen Typen, die nur der Compiler kennt oder deren Spezifikation sehr lang ist. Mit der Syntax impl Trait kannst du prägnant angeben, dass eine Funktion einen Typ zurückgibt, der das Trait Iterator implementiert, ohne dass du einen sehr langen Typ schreiben musst.

Du kannst impl Trait jedoch nur verwenden, wenn du einen einzigen Typ zurückgibst. Beispielsweise würde dieser Code, der entweder einen NewsArticle oder einen SocialPost mit dem Rückgabetyp impl Summary zurückgibt, nicht funktionieren:

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, von {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins gewinnen die Stanley-Cup-Meisterschaft!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "Die Pittsburgh Penguins sind erneut die beste \
                 Eishockeymannschaft in der NHL.",
            ),
        }
    } else {
        SocialPost {
            username: String::from("horse_ebooks"),
            content: String::from("natürlich, wie du wahrscheinlich schon weißt"),
            reply: false,
            repost: false,
        }
    }
}
}

Die Rückgabe entweder eines NewsArticle oder eines SocialPost ist aufgrund von Einschränkungen hinsichtlich der Implementierung der Syntax impl Trait im Compiler nicht erlaubt. Wie man eine Funktion mit diesem Verhalten schreibt, wird in „Verwendung von Trait-Objekten zur Abstraktion über gemeinsames Verhalten“ in Kapitel 18 behandelt.

Verwenden von Trait Bounds zur bedingten Implementierung von Methoden

Durch Verwenden einer Trait Bound mit einem impl-Block, der generische Typparameter verwendet, können wir Methoden bedingt für Typen implementieren, die das angegebene Trait implementieren. Beispielsweise implementiert der Typ Pair<T> in Listing 10-15 immer die Funktion new, um eine neue Instanz von Pair<T> zurückzugeben (erinnere dich an „Methoden-Syntax“ in Kapitel 5, dass Self ein Typ-Alias für den Typ des impl-Blocks ist, der in diesem Fall Pair<T> ist). Aber im nächsten impl-Block implementiert Pair<T> die Methode cmp_display nur, wenn ihr innerer Typ T die Traits PartialOrd und Display implementiert, die den Vergleich bzw. eine Ausgabe ermöglichen.

Dateiname: src/lib.rs

#![allow(unused)]
fn main() {
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("Das größte Element ist x = {}", self.x);
        } else {
            println!("Das größte Element ist y = {}", self.y);
        }
    }
}
}

Listing 10-15: Bedingte Implementierung von Methoden für einen generischen Typ in Abhängigkeit von Trait Bounds

Wir können auch ein Trait für beliebige Typen bedingt implementieren, die ein anderes Trait implementieren. Implementierungen eines Traits für Typen, die Trait Bounds erfüllen, werden als Pauschal-Implementierungen (blanket implementations) bezeichnet und kommen in der Rust-Standardbibliothek ausgiebig zur Anwendung. Beispielsweise implementiert die Standardbibliothek das Trait ToString für jeden Typ, der das Trait Display implementiert. Der impl-Block in der Standardbibliothek sieht in etwa so aus:

impl<T: Display> ToString for T {
    // --abschneiden--
}

Da die Standardbibliothek diese Pauschal-Implementierungen hat, können wir die Methode to_string, die durch das Trait ToString definiert ist, bei jedem Typ aufrufen, der das Trait Display implementiert. Zum Beispiel können wir ganze Zahlen in ihre entsprechenden String-Werte umwandeln, weil ganze Zahlen Display implementieren:

#![allow(unused)]
fn main() {
let s = 3.to_string();
}

Pauschal-Implementierungen erscheinen in der Dokumentation des Traits im Abschnitt „Implementierer“ (implementors).

Mithilfe von Traits und Trait Bounds können wir Code schreiben, der generische Typparameter verwendet, um Duplikationen zu reduzieren, aber auch dem Compiler gegenüber angeben, dass der generische Typ ein bestimmtes Verhalten haben soll. Der Compiler kann dann die Trait Bounds verwenden, um zu überprüfen, ob alle konkreten Typen, die von unserem Code verwendet werden, das richtige Verhalten aufweisen. In dynamisch typisierten Sprachen würden wir einen Laufzeitfehler erhalten, wenn wir eine Methode bei einem Typ aufrufen, der die Methode nicht definiert hat. Rust verschiebt diese Fehler jedoch in die Kompilierzeit und verlangt damit, dass wir die Probleme beheben, bevor unser Code überhaupt lauffähig ist. Außerdem müssen wir keinen Code schreiben, der das Verhalten zur Laufzeit überprüft, da wir es bereits zur Kompilierzeit überprüft haben. Auf diese Weise wird die Performanz verbessert, ohne die Flexibilität der generischen Datentypen aufgeben zu müssen.