Das #[derive(...)]-Attribut ist eines der mächtigsten und meistgenutzten Werkzeuge in Rust. Eine einzige Zeile vor einer Struct-Deklaration ersetzt dutzende oder hunderte Zeilen Boilerplate. Die Stdlib bringt derive-fähige Versionen der wichtigsten Standard-Traits mit: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default. Wer einen Typ definiert, beginnt fast immer mit einer Derive-Zeile. Dieser Artikel zeigt jeden dieser Traits einzeln, erklärt seine Bedeutung, welche Voraussetzungen die Felder erfüllen müssen und wann manuelle Implementierung sinnvoller ist.

Was derive macht

#[derive(Debug)] generiert eine impl Debug for Type { ... }-Implementierung automatisch zur Compile-Zeit. Vor dem derive-Makro müsstest du das von Hand schreiben — viel Boilerplate, leicht falsch.

Rust Mit und ohne derive
#[derive(Debug)]
struct Person {
    name: String,
    alter: u32,
}

fn main() {
    let p = Person { name: "Anna".into(), alter: 28 };
    println!("{p:?}");    // "Person { name: \"Anna\", alter: 28 }"
}

Ohne #[derive(Debug)] müsstest du implementieren:

Rust Manuell
struct Person { name: String, alter: u32 }

impl std::fmt::Debug for Person {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.debug_struct("Person")
            .field("name", &self.name)
            .field("alter", &self.alter)
            .finish()
    }
}

Bei vielen Feldern oder vielen Typen wird das schnell unübersichtlich. derive macht das automatisch.

Voraussetzung: Felder müssen den Trait selbst implementieren

derive ist nicht magisch — es delegiert an die Felder. Ein Feld vom Typ T muss den gewünschten Trait selbst implementieren:

Rust Voraussetzung
struct OhneDebug;       // implementiert KEIN Debug

// #[derive(Debug)]
// struct A { feld: OhneDebug }      // Compile-Fehler — Feld hat kein Debug

#[derive(Debug)]
struct B { feld: i32 }                // ok — i32 hat Debug

Der Compiler-Fehler ist klar:

Rust
error[E0277]: `OhneDebug` doesn't implement `Debug`
   |
   = help: the trait `Debug` is not implemented for `OhneDebug`
   = note: required for the derived impl of `Debug` for `A`

Die wichtigsten derivable Traits

Debug — Entwickler-Output

Rust Debug
#[derive(Debug)]
struct Punkt { x: f64, y: f64 }

fn main() {
    let p = Punkt { x: 1.0, y: 2.0 };
    println!("{p:?}");       // "Punkt { x: 1.0, y: 2.0 }"
    println!("{p:#?}");      // Pretty-Format mit Newlines
}

Standard-Format mit {:?}, Pretty-Format mit {:#?}. Für Entwickler-Output, nicht End-User.

Clone und Copy

Rust Clone/Copy
#[derive(Clone, Copy)]
struct Koord { x: i32, y: i32 }

fn main() {
    let a = Koord { x: 1, y: 2 };
    let b = a;        // Copy — a weiterhin verwendbar
    println!("{} {}", a.x, b.x);
}

Copy setzt voraus, dass alle Felder Copy sind. Sobald ein String oder Vec als Feld auftaucht, ist Copy nicht möglich — Clone aber schon.

Rust Nur Clone
#[derive(Clone)]
struct Person { name: String, alter: u32 }

fn main() {
    let a = Person { name: "Anna".into(), alter: 28 };
    let b = a.clone();        // explizit klonen
    println!("{} {}", a.name, b.name);
}

PartialEq und Eq

Rust Gleichheit
#[derive(PartialEq, Eq)]
struct Konto { id: u64 }

fn main() {
    let a = Konto { id: 42 };
    let b = Konto { id: 42 };
    assert_eq!(a, b);         // verwendet PartialEq::eq
}
  • PartialEq — Vergleich mit == und !=. Auf Felder reflektiert.
  • Eq — Marker-Trait: „dies ist eine Total-Equivalence-Relation". Voraussetzung: kein f32/f64-Feld (wegen NaN). Pflicht, wenn der Typ als HashMap-Key dienen soll.

PartialOrd und Ord

Rust Ordnung
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

fn main() {
    let a = Version { major: 1, minor: 2, patch: 0 };
    let b = Version { major: 1, minor: 3, patch: 0 };
    assert!(a < b);
}

derive(PartialOrd, Ord) vergleicht feldweise in Deklarations-Reihenfolge (lexikographisch). Erst major, bei Gleichheit minor, dann patch. Sehr nützlich bei sortierbaren Domänen-Typen.

Hash

Rust HashMap-Key
use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
struct UserId(u64);

fn main() {
    let mut map: HashMap<UserId, String> = HashMap::new();
    map.insert(UserId(42), "Anna".into());
    assert_eq!(map.get(&UserId(42)), Some(&"Anna".to_string()));
}

Hash plus Eq ist Pflicht für HashMap-Keys und HashSet-Elemente. Beide aus der Standard-Bibliothek.

Default

Rust Default-Werte
#[derive(Default, Debug)]
struct Config {
    timeout_ms: u32,         // Default = 0
    retries: u32,             // Default = 0
    host: String,             // Default = ""
}

fn main() {
    let c = Config::default();
    println!("{c:?}");      // "Config { timeout_ms: 0, retries: 0, host: \"\" }"
}

Default setzt jedes Feld auf Type::default(). Funktioniert nur, wenn alle Feld-Typen Default implementieren. Für eigene Default-Werte: manuelle Impl.

Die häufigste Derive-Kombination

Für „normale" Wert-artige Typen:

Rust Standard-Set
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct User {
    id: u64,
    email: String,
}

Diese fünf Traits zusammen sind das Standard-Set für Domain-Typen:

  • Debug — für println!("{:?}", ...).
  • Clone — explizites Duplizieren möglich.
  • PartialEq + Eq — Vergleich mit ==, HashMap-Key-fähig.
  • Hash — HashMap/HashSet-Verwendung.

Wenn der Typ auch Copy sein soll (alle Felder Copy):

Rust Copy-Typen
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Punkt { x: i32, y: i32 }

Wenn sortierbar:

Rust Sortierbar
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Version { major: u32, minor: u32, patch: u32 }

Wann manuelles Impl?

Trotz derive gibt es Fälle für manuelle Implementierung:

1. Custom-Display

Rust Display ist nicht derivable
use std::fmt;

struct Geld { eur: u32, cent: u8 }

impl fmt::Display for Geld {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{},{:02} €", self.eur, self.cent)
    }
}

Display hat kein derive — es muss manuell sein, weil die Stdlib keine sinnvolle Default-Formatierung wählen kann.

2. Custom-Hash für Spezial-Cases

Rust Custom Hash
use std::hash::{Hash, Hasher};

struct Username(String);

impl PartialEq for Username {
    fn eq(&self, other: &Self) -> bool {
        self.0.to_lowercase() == other.0.to_lowercase()
    }
}

impl Eq for Username {}

impl Hash for Username {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.to_lowercase().hash(state);
    }
}

Wenn eq case-insensitive sein soll, muss auch hash case-insensitive sein — sonst inkonsistent. Beides manuell.

3. Felder ignorieren

derive(PartialEq) vergleicht alle Felder. Wer einzelne ignorieren will (z. B. einen Cache-Hash, der nicht zur Identität gehört), muss manuell implementieren.

Custom-Derive-Crates

Über die Stdlib-derives hinaus gibt es proc-macro-Crates mit eigenen derives:

Rust serde-Derive
# // Erfordert Crate: serde = { version = "1", features = ["derive"] }
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: u64,
    name: String,
}

Andere häufige Custom-derives:

  • serde::Serialize/Deserialize — JSON, YAML, BSON, MessagePack, ...
  • thiserror::Error — Error-Type mit Display + Error-Trait automatisch.
  • clap::Parser/Args — CLI-Argumente parsen.
  • derive_builder::Builder — Builder-Pattern automatisch generieren.
  • derive_moreAdd, Sub, From, ... automatisch generieren.

Diese sind keine Standard-Bibliothek, aber Quasi-Standard in modernem Rust.

Praxis: derive im echten Code

Domain-Typ mit Standard-Set

Rust Order
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OrderId(u64);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Order {
    pub id: OrderId,
    pub kunde: String,
    pub total_cent: i64,
}

OrderId als Newtype, Order als Field-Struct — beide mit Standard-Set für volle Verwendbarkeit.

Konfiguration mit Default

Rust App-Config
#[derive(Debug, Clone, Default)]
pub struct AppConfig {
    pub host: String,
    pub port: u16,
    pub workers: u32,
    pub tls_enabled: bool,
}

fn main() {
    let cfg = AppConfig {
        host: "localhost".into(),
        ..Default::default()        // andere Felder = Default
    };
    println!("{cfg:?}");
}

Default + Update-Syntax — sehr idiomatisch für „nur einige Felder setzen, Rest Default".

Versionierung mit Ord

Rust Version-Sortierung
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Version {
    pub major: u32,
    pub minor: u32,
    pub patch: u32,
}

fn main() {
    let mut v = vec![
        Version { major: 1, minor: 2, patch: 0 },
        Version { major: 1, minor: 0, patch: 5 },
        Version { major: 0, minor: 9, patch: 9 },
        Version { major: 1, minor: 2, patch: 1 },
    ];
    v.sort();
    for ver in &v {
        println!("{}.{}.{}", ver.major, ver.minor, ver.patch);
    }
    // 0.9.9, 1.0.5, 1.2.0, 1.2.1
}

Lexikographische Sortierung in Deklarations-Reihenfolge — passt perfekt für Versions-Nummern.

Coord mit Copy

Rust Coord
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Coord { pub x: f64, pub y: f64 }

Klein, Copy-fähig (beide Felder Copy), keine Eq/Hash (wegen f64-NaN-Problem).

Cache-Eintrag mit Hash

Rust Cache-Key
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey {
    pub user_id: u64,
    pub ressource: String,
}

fn main() {
    let mut cache: HashMap<CacheKey, Vec<u8>> = HashMap::new();
    let key = CacheKey { user_id: 42, ressource: "profile".into() };
    cache.insert(key.clone(), vec![1, 2, 3]);
    assert!(cache.contains_key(&key));
}

Composite-Key für HashMap — alle Felder selbst Hash + Eq, also kann der Composite-Key beide Traits derive-en.

Marker-Wert mit Default + Debug

Rust Default + Debug
#[derive(Debug, Default)]
pub struct Counter {
    pub erfolge: u64,
    pub fehler: u64,
    pub gesamt: u64,
}

impl Counter {
    pub fn erfolg(&mut self) {
        self.erfolge += 1;
        self.gesamt += 1;
    }
    pub fn fehler(&mut self) {
        self.fehler += 1;
        self.gesamt += 1;
    }
}

Counter::default() startet alle Zähler bei 0.

Error-Type mit Debug

Rust Error
#[derive(Debug)]
pub enum AppError {
    NetzwerkFehler { code: u32, nachricht: String },
    ParseFehler(String),
    NichtGefunden,
}

Enums sind ebenfalls derive-fähig. Für End-User-Output zusätzlich impl Display.

Vergleichbare Domain-IDs

Rust Order-Priority
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Priority(u8);

fn main() {
    let mut v = vec![Priority(3), Priority(1), Priority(5), Priority(2)];
    v.sort();
    assert_eq!(v, vec![Priority(1), Priority(2), Priority(3), Priority(5)]);
}

Newtype-Wrapper mit voller Standard-Trait-Implementierung — alle relevanten Sortier-, Hash-, Vergleichs-Operationen verfügbar.

Generischer Container

Rust Generic mit Bounds
#[derive(Debug, Clone)]
pub struct Paginated<T: Clone> {
    pub items: Vec<T>,
    pub gesamt: u64,
}

derive(Clone) funktioniert generisch — verlangt aber T: Clone als Bound (vom Compiler automatisch hinzugefügt).

serde-derives für Web-API

Rust serde-Beispiel
# // Erfordert serde = { version = "1", features = ["derive"] }
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserDto {
    pub id: u64,
    pub username: String,
    pub email: String,
}

In Web-Anwendungen Standard: ein DTO-Struct mit Serialize und Deserialize für JSON-Konvertierung.

Besonderheiten

derive ist Compile-Zeit-Code-Gen.

Das #[derive(Debug)]-Attribut sagt dem Compiler, eine impl Debug-Block zu generieren. Im Maschinencode ist da kein Unterschied zu handgeschriebener Implementierung. Pure Boilerplate-Reduktion.

Felder müssen den Trait selbst implementieren.

#[derive(Hash)] struct A { f: B } funktioniert nur, wenn B: Hash. Sonst Compile-Fehler. Wer einen Trait derive-en will, muss bei allen Feldern dafür sorgen.

Standard-Set: Debug, Clone, PartialEq, Eq, Hash.

Für die meisten Domain-Typen sind diese fünf der Default. Plus Copy wenn alle Felder Copy sind, plus PartialOrd, Ord wenn sortierbar, plus Default wenn ein Zero-Konstruktor sinnvoll ist.

Copy impliziert Clone.

Du musst #[derive(Copy, Clone)] zusammen schreiben — reines Copy ohne Clone ist Compile-Fehler. Die Stdlib hat eine Trait-Hierarchie: Copy: Clone.

Eq erfordert PartialEq.

Ähnlich wie bei Copy/Clone: Eq ist Marker, PartialEq ist die eigentliche Implementierung. #[derive(PartialEq, Eq)] zusammen. Floats können nur PartialEq haben — Eq ist verboten wegen NaN.

Display ist NICHT derivable.

Anders als Debug (für Entwickler-Output) muss Display (für End-User-Output) manuell implementiert werden. Die Stdlib kann keine sinnvolle Default-Formatierung wählen — das ist eine Design-Entscheidung pro Typ.

Reihenfolge bei derive ist irrelevant.

#[derive(Clone, Debug, Hash)] und #[derive(Hash, Clone, Debug)] sind identisch. Aber idiomatisch: in der gleichen Reihenfolge wie üblich (Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default — meist alphabetisch sortiert).

Custom-derives erweitern das System.

Über proc-macros lassen sich beliebige derive-Implementierungen schreiben. serde::Serialize, thiserror::Error, clap::Parser sind klassische Beispiele — aus der externen Crate kommt der Macro, der das impl generiert.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Structs & Methoden

Zur Übersicht