Rust Traits & Generics

6 Kernkonzepte
Die wichtigsten Konzepte für Traits und Generics in Rust: trait · impl · derive · Generic <T> · where · Associated Types Traits und Generics sind das Herzstück der Polymorphie in Rust. Sie ermöglichen wiederverwendbaren, typsicheren Code und sind essenziell für die Entwicklung flexibler Bibliotheken und Anwendungen.

Traits – Schnittstellen definieren

trait · fn · &self
// Trait definieren trait Summary { fn summarize(&self) -> String; fn default_summary(&self) -> String { String::from("Standard-Implementierung") } }

Traits definieren gemeinsame Funktionalität, die verschiedene Typen teilen können. Sie sind ähnlich wie Interfaces in anderen Sprachen und ermöglichen Polymorphie zur Compile-Zeit.

Beispiele
// Trait mit Default-Implementierung
trait Description {
fn describe(&self) -> String;
fn short_description(&self) -> String {
String::from("Kurzbeschreibung")
}
}
// Trait mit mehreren Methoden
trait Area {
fn area(&self) -> f64;
fn perimeter(&self) -> f64;
}
// Trait mit &self und &mut self
trait Counter {
fn count(&self) -> usize;
fn increment(&mut self);
}
Tipp: Traits können Default-Implementierungen für Methoden bereitstellen. Typen, die das Trait implementieren, können die Standardimplementierung überschreiben oder übernehmen.

impl – Traits für Typen implementieren

impl Trait for Type
// Struktur definieren struct NewsArticle { headline: String, location: String, } // Trait implementieren impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{} - {}", self.headline, self.location) } }

impl wird verwendet, um ein Trait für einen bestimmten Typ zu implementieren. Der Typ muss alle Methoden des Traits implementieren (außer solche mit Default-Implementierung).

Beispiele
// Struct mit Trait-Implementierung
struct Circle { radius: f64 }
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
}
// Mehrere Traits für einen Typ
impl Display for Circle {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "Kreis(r={})", self.radius)
}
}
// Blanket Implementation (für alle Typen, die T implementieren)
impl<T: Summary> Summary for Vec<T> {
fn summarize(&self) -> String {
format!("[{}]", self.iter().map(Summary::summarize).collect::<Vec<String>>().join(", "))
}
}
Tipp: Sie können Traits auch für Typen aus anderen Crates implementieren (orphan rule). Blanket Implementations ermöglichen es, Traits für ganze Typklassen zu implementieren.

derive – Traits automatisch ableiten

#[derive(Debug, Clone, Copy)]
// Attribute für automatische Trait-Implementierung #[derive(Debug, Clone, PartialEq)] struct User { id: u32, name: String, }

derive ist ein Attribut, das dem Compiler mitteilt, bestimmte Traits automatisch für einen Typ zu implementieren. Das spart viel Boilerplate-Code.

Häufige derive-Traits

Trait Beschreibung
Debug Formatierung mit {:?} für println! und Debugging
Clone Klonen des Typs mit .clone()
Copy Bitweise Kopie (nur für Typen, die auf dem Stack liegen)
PartialEq Gleichheitsvergleich mit == und !=
Eq Totale Gleichheit (erweitert PartialEq)
PartialOrd Vergleichsoperatoren wie <, >, <=, >=
Ord Totale Ordnung (erweitert PartialOrd)
Hash Hash-Wert für HashMap und HashSet
Default Standardwert mit .default()
Beispiele
// Mehrere Traits ableiten
#[derive(Debug, Clone, PartialEq, Hash)]
struct Person {
name: String,
age: u32,
}
// Verwendung von Debug
let p = Person { name: "Anna".to_string(), age: 30 };
println!("{:?}", p); // Person { name: "Anna", age: 30 }
// Copy (nur für einfache Typen)
#[derive(Debug, Copy, Clone)]
struct Point { x: i32, y: i32 }
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copy (kein Move)
println!("{:?}, {:?}", p1, p2);
// Default
#[derive(Default, Debug)]
struct Config {
host: String,
port: u16,
}
let default_config = Config::default();
Tipp: derive funktioniert nur für Traits, die vom Compiler unterstützt werden. Für eigene Traits müssen Sie die Implementierung selbst schreiben oder ein Makro-Crate wie derive_more verwenden.

Generics – Typparameter <T>

fn<T>(x: T) · struct<T>
// Generische Funktion fn identity<T>(value: T) -> T { return value; } // Generische Struktur struct Wrapper<T> { value: T }

Generics ermöglichen die Definition von Funktionen, Strukturen und Enums, die mit verschiedenen Typen arbeiten können. Sie bieten Typsicherheit bei maximaler Flexibilität.

Beispiele
// Generische Funktion mit zwei Typen
fn swap<T, U>(a: T, b: U) -> (U, T) {
(b, a)
}
// Generische Struktur
struct Box<T> { content: T }
impl<T> Box<T> {
fn new(content: T) -> Self {
Box { content }
}
fn unwrap(self) -> T {
self.content
}
}
// Generisches Enum
enum Result<T, E> {
Ok(T),
Err(E),
}
// Generische Methode
impl<T> Wrapper<T> {
fn map<U>(self, f: impl Fn(T) -> U) -> Wrapper<U> {
Wrapper { value: f(self.value) }
}
}
Tipp: Generics werden zur Compile-Zeit monomorphisiert – das bedeutet, es entsteht kein Laufzeit-Overhead. Verwenden Sie Generics für wiederverwendbare, typsichere Komponenten.

where Clauses – Trait-Bounds lesbar machen

where T: Trait + Clone
// where-Clause für bessere Lesbarkeit fn process<T, U>(a: T, b: U) -> String where T: Display + Clone, U: Debug + PartialEq, { /* ... */ }

where Clauses sind eine alternative Syntax für Trait-Bounds. Sie verbessern die Lesbarkeit, besonders bei komplexen Generics mit mehreren Bounds.

Beispiele
// where-Clause mit mehreren Bounds
fn compare_and_display<T, U>(a: &T, b: &U)
where
T: Display + PartialEq,
U: Debug + PartialEq,
{
println!("T: {}, U: {:?}", a, b);
if a == b { println!("Gleich"); }
}
// where in impl-Block
impl<T> Container<T>
where
T: Display + Clone,
{
fn print(&self) {
println!("{}", self.value);
}
}
// where mit associated Types
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
fn process_items<I>(iter: &mut I)
where
I: Iterator,
I::Item: Display,
{
while let Some(item) = iter.next() {
println!("{}", item);
}
}
Tipp: Verwenden Sie where-Clauses für komplexe Generics mit vielen Bounds – das verbessert die Lesbarkeit erheblich. Besonders nützlich bei generischen Funktionen mit mehreren Typparametern.

Associated Types – Typen in Traits

type Item · impl Iterator
// Trait mit Associated Type trait Container { type Item; fn get(&self) -> &Self::Item; } // Implementierung impl Container for Box<i32> { type Item = i32; fn get(&self) -> &Self::Item { &self.content } }

Associated Types sind Typen, die innerhalb eines Traits definiert werden und von der Implementierung festgelegt werden. Sie machen Traits flexibler und lesbarer.

Beispiele
// Iterator Trait mit Associated Type
trait MyIterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
// Implementierung für einen Vektor
struct VecIterator<T> {
items: Vec<T>,
index: usize,
}
impl<T> MyIterator for VecIterator<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.items.len() {
let item = &self.items[self.index];
self.index += 1;
Some(item.clone())
} else {
None
}
}
}
// Trait mit Associated Type und Generic
trait Graph {
type Node;
type Edge;
fn nodes(&self) -> Vec<Self::Node>;
fn edges(&self) -> Vec<Self::Edge>;
}
// Verwendung mit where-Clause
fn process_graph<G>(graph: &G)
where
G: Graph,
G::Node: Display,
{
for node in graph.nodes() {
println!("Node: {}", node);
}
}
Tipp: Associated Types sind ideal für Traits, bei denen der Typ logisch zum Trait gehört (wie Item bei Iterator). Sie reduzieren die Anzahl der generischen Parameter und machen den Code lesbarer.

Rust Traits & Generics im Überblick

trait Schnittstelle definieren
fn method(&self)
impl Trait implementieren
impl Trait for Type
derive Automatisch ableiten
Debug, Clone, PartialEq
<T> Generischer Typ
fn<T>(x: T)
where Trait-Bounds
T: Display + Clone
type Associated Type
type Item;

Quick Summary

trait
Schnittstelle
impl
Implementieren
derive
Automatisch ableiten
<T>
Generics
where
Trait-Bounds
type
Associated Types
trait Summary { fn summarize(&self) -> String; } · #[derive(Debug, Clone)]