Der Drop-Trait ist das, was Ownership operativ macht: er definiert, wie ein Wert freigegeben wird, sobald sein Besitzer den Scope verlässt. Anders als bei Garbage Collection passiert das deterministisch zur Compile-Zeit-festgelegten Stelle. File-Handles werden zuverlässig geschlossen, Mutex-Guards freigegeben, Datenbank-Transaktionen committed oder rollbacked — alles ohne expliziten Cleanup-Code im Aufrufer. Dieses Pattern heißt RAII (Resource Acquisition Is Initialization) und ist eines der mächtigsten Werkzeuge der Sprache.

Der Drop-Trait

Aus der Stdlib (vereinfacht):

Rust Drop-Definition
pub trait Drop {
    fn drop(&mut self);
}

Ein Typ, der Drop implementiert, gibt eine Cleanup-Methode an. Der Compiler ruft sie automatisch beim Scope-Ende auf.

Rust Eigene Drop-Impl
struct Verbindung {
    name: String,
}

impl Drop for Verbindung {
    fn drop(&mut self) {
        println!("Schließe Verbindung: {}", self.name);
    }
}

fn main() {
    let v = Verbindung { name: "API".into() };
    println!("Arbeite mit {}", v.name);
}
// Ausgabe:
// Arbeite mit API
// Schließe Verbindung: API

Die drop-Methode läuft beim Funktions-Ende automatisch. Du musst sie nicht manuell aufrufen — der Compiler erkennt den Scope-Übergang und fügt den Aufruf ein.

Wann genau läuft drop?

Drop wird in folgenden Situationen ausgelöst:

Am Scope-Ende

Rust Block-Scope
fn main() {
    {
        let v = Verbindung { name: "Inner".into() };
    }   // <-- v.drop() läuft hier
    println!("Nach dem Block");
}
# struct Verbindung { name: String }
# impl Drop for Verbindung { fn drop(&mut self) { println!("Drop: {}", self.name); } }

Jede }-Klammer, die einen Scope schließt, droppt alle ihre lokalen non-Copy-Bindungen.

Beim Funktions-Return

Rust Return-Drop
fn main() {
    let v = Verbindung { name: "Funktion".into() };
    return;
    // Auch wenn return frühzeitig — v wird gedroppt.
}
# struct Verbindung { name: String }
# impl Drop for Verbindung { fn drop(&mut self) { println!("Drop: {}", self.name); } }

Bei Move in eine andere Funktion

Rust Move-Drop
fn nehmen(v: Verbindung) {
    println!("nehmen: {}", v.name);
}   // <-- v wird hier gedroppt

fn main() {
    let v = Verbindung { name: "Param".into() };
    nehmen(v);              // Move passt Besitz weiter
    println!("zurück in main");
}
# struct Verbindung { name: String }
# impl Drop for Verbindung { fn drop(&mut self) { println!("Drop: {}", self.name); } }

v wird in nehmen gemoved, und droppt im Funktions-Scope von nehmen, nicht in main.

Bei Panic (außer mit panic = abort)

Bei einem Panic werden alle lokalen Bindungen des aktuellen Stack-Frames gedroppt, bevor der Panic in den nächsthöheren Frame propagiert. So bleibt das Programm im Sinne von Ressourcen-Cleanup konsistent.

Rust Panic + Drop
fn main() {
    let v = Verbindung { name: "Panic-Test".into() };
    panic!("Etwas ist schiefgelaufen");
    // v wird trotzdem gedroppt, bevor der Panic Stack-unwinds.
}
# struct Verbindung { name: String }
# impl Drop for Verbindung { fn drop(&mut self) { println!("Drop: {}", self.name); } }

In Cargo.toml mit [profile.release] panic = "abort" wird der Prozess sofort beendet, ohne Drop-Aufrufe — eine bewusste Wahl für maximale Performance.

Bei expliziten Aufruf von std::mem::drop

Rust Explizites Drop
fn main() {
    let v = Verbindung { name: "Manuell".into() };
    std::mem::drop(v);          // Sofortiges Drop
    println!("Nach drop");
    // println!("{}", v.name);   // Fehler — v ist weg.
}
# struct Verbindung { name: String }
# impl Drop for Verbindung { fn drop(&mut self) { println!("Drop: {}", self.name); } }

std::mem::drop ist nicht direkt der Drop::drop-Methode — es ist eine kleine Funktion, die den Wert konsumiert und sich darauf verlässt, dass der Compiler ihn am Funktions-Ende droppt. Trick: die Funktion ist trivial:

Rust std::mem::drop-Implementierung
pub fn drop<T>(_x: T) {}
// Der Parameter _x wird in drop gemoved und am Funktions-Ende gedroppt.

Drop-Reihenfolge: LIFO

Bei mehreren Werten in einem Scope werden sie in umgekehrter Deklarations-Reihenfolge gedroppt:

Rust LIFO-Drop
struct Laut(&'static str);
impl Drop for Laut {
    fn drop(&mut self) {
        println!("Drop: {}", self.0);
    }
}

fn main() {
    let a = Laut("a");
    let b = Laut("b");
    let c = Laut("c");
    println!("--- Ende ---");
}
// Ausgabe:
// --- Ende ---
// Drop: c
// Drop: b
// Drop: a

Das ist wichtig, wenn Ressourcen aufeinander aufbauen: c zuerst, dann b, dann a — wenn c z. B. ein File-Handle ist, das in einen Buffer in b schreibt, sollte c zuerst weg sein.

Struct-Felder: Deklarations-Reihenfolge

Bei einem Drop-Impl auf einem Struct werden die Felder in Deklarations-Reihenfolge gedroppt, nachdem das drop-Methoden-Body gelaufen ist:

Rust Struct-Drop
struct Container {
    erstes: Laut,
    zweites: Laut,
}

impl Drop for Container {
    fn drop(&mut self) {
        println!("Container::drop läuft (vor Feld-Drops)");
    }
}

# struct Laut(&'static str);
# impl Drop for Laut { fn drop(&mut self) { println!("Drop: {}", self.0); } }

fn main() {
    let _c = Container {
        erstes: Laut("a"),
        zweites: Laut("b"),
    };
    // Reihenfolge beim Drop:
    // 1. Container::drop()
    // 2. erstes (a)
    // 3. zweites (b)
}

Erst die eigene drop-Methode des Outer-Structs, dann die Felder in Deklarations-Reihenfolge.

Drop ist nicht aufrufbar

Eine wichtige Subtilität: du kannst drop als Methode nicht direkt aufrufen:

Rust Manuelles drop verboten
# struct Verbindung;
# impl Drop for Verbindung { fn drop(&mut self) {} }
fn main() {
    let v = Verbindung;
    // v.drop();          // Fehler! Explicit use of destructor method.
    std::mem::drop(v);     // OK — geht über die std::mem::drop-Funktion
}

Der Grund: würde v.drop() direkt erlaubt sein, könnte der Wert hinterher noch verwendet werden — der Compiler weiß ja nicht, dass drop „besonders" ist. Mit std::mem::drop(v) wird v in die Funktion gemoved, und der Compiler sieht das als regulären Move — danach ist v weg.

std::mem::forget — Drop überspringen

Es gibt einen seltsamen, aber legalen Weg, Drop zu verhindern: std::mem::forget.

Rust forget
# struct Verbindung;
# impl Drop for Verbindung { fn drop(&mut self) { println!("Drop läuft"); } }
fn main() {
    let v = Verbindung;
    std::mem::forget(v);        // v wird NICHT gedroppt.
    println!("Nach forget");
}
// Ausgabe:
// Nach forget
// (kein „Drop läuft"!)

forget verhindert den Drop, ohne den Speicher freizugeben. Wichtig:

  • Es ist safe Rust — kein unsafe nötig.
  • Es ist ein Memory-Leak — die Heap-Allocations (falls vorhanden) werden nie freigegeben.
  • Anwendungsfälle: FFI mit C-Code, der Ownership übernimmt. Manuelle Speicher-Manipulation in Unsafe-Wrappers.

Memory-Leaks sind in Rust safe im Borrow-Checker-Sinne — sie verletzen keine der drei Ownership-Regeln. Sie sind „nur" ein logischer Bug.

Drop und Move

Wichtig: nach einem Move wird nicht gedroppt — der Wert ist ja jetzt woanders.

Rust Move + Drop
# struct Laut(&'static str);
# impl Drop for Laut { fn drop(&mut self) { println!("Drop: {}", self.0); } }
fn nehmen(_l: Laut) {
    println!("In nehmen");
}   // _l wird hier gedroppt

fn main() {
    let l = Laut("x");
    nehmen(l);           // l wird in nehmen gemoved
    println!("Nach nehmen");
    // l ist hier nicht mehr nutzbar — und es wird auch nicht erneut gedroppt.
}
// Ausgabe:
// In nehmen
// Drop: x
// Nach nehmen

Der Compiler verfolgt pro Bindung, ob sie noch besessen wird. Eine gemovedte Bindung wird nicht am Scope-Ende erneut gedroppt — das wäre Double-Free.

Praxis: Drop im echten Code

File-Handle automatisch schließen

Rust File-Drop
use std::fs::File;
use std::io::Write;

fn schreibe_log(zeile: &str) -> std::io::Result<()> {
    let mut datei = File::create("/tmp/app.log")?;
    writeln!(datei, "{zeile}")?;
    Ok(())
}
// datei wird hier gedroppt → File-Handle wird OS-seitig geschlossen.

Ein klassisches RAII-Beispiel: die File-Stdlib-Implementierung hat Drop, das den OS-File-Descriptor freigibt. Selbst wenn write_all einen Fehler liefert und ? ein frühzeitig Return triggert — Drop läuft trotzdem.

Mutex-Guard

Rust Lock-Drop
use std::sync::Mutex;

fn main() {
    let counter = Mutex::new(0);

    {
        let mut guard = counter.lock().unwrap();
        *guard += 1;
    }   // guard wird hier gedroppt → Mutex freigegeben

    // Anderer Code kann jetzt counter locken
    let again = counter.lock().unwrap();
    println!("{}", *again);
}

Der MutexGuard ist ein RAII-Wrapper: solange er lebt, ist der Mutex gehalten. Beim Drop wird der Lock freigegeben. Niemand vergisst je, einen Lock freizugeben — der Compiler kümmert sich.

Datenbank-Transaktion mit Rollback-on-Drop

Rust DB-Transaktion
struct Tx {
    id: u64,
    commited: bool,
}

impl Tx {
    fn beginnen() -> Self {
        println!("BEGIN tx={}", 42);
        Tx { id: 42, commited: false }
    }
    fn commit(mut self) {
        self.commited = true;
        println!("COMMIT tx={}", self.id);
    }
}

impl Drop for Tx {
    fn drop(&mut self) {
        if !self.commited {
            println!("ROLLBACK tx={}", self.id);
        }
    }
}

fn beispiel(soll_committen: bool) {
    let tx = Tx::beginnen();
    // ... Datenbank-Operationen ...
    if soll_committen {
        tx.commit();
    }
    // Wenn nicht committed: tx.drop() läuft -> ROLLBACK
}

Wenn der Code irgendwo zwischen beginnen und commit mit ? oder Panic aussteigt, läuft Drop automatisch und rollbackt. Eine Transaktion kann nicht „vergessen" werden.

Scope-Guard für temporäre State-Änderungen

Rust Scope-Guard
struct Indenter<'a> {
    level: &'a std::cell::Cell<u32>,
}

impl<'a> Indenter<'a> {
    fn neu(level: &'a std::cell::Cell<u32>) -> Self {
        level.set(level.get() + 1);
        Indenter { level }
    }
}

impl<'a> Drop for Indenter<'a> {
    fn drop(&mut self) {
        self.level.set(self.level.get() - 1);
    }
}

fn main() {
    let level = std::cell::Cell::new(0);
    {
        let _i = Indenter::neu(&level);
        assert_eq!(level.get(), 1);
        {
            let _j = Indenter::neu(&level);
            assert_eq!(level.get(), 2);
        }
        assert_eq!(level.get(), 1);
    }
    assert_eq!(level.get(), 0);
}

Wer eine Eigenschaft (Indent-Level, Logging-Context) temporär ändern will, packt sie in einen Scope-Guard. Beim Verlassen des Scopes wird die Änderung garantiert zurückgenommen.

Cleanup nach kritischen Operations

Rust Backup-Wiederherstellen
struct BackupRestore {
    original: Vec<u8>,
    target: std::path::PathBuf,
    committed: bool,
}

impl BackupRestore {
    fn neu(pfad: std::path::PathBuf) -> std::io::Result<Self> {
        let original = std::fs::read(&pfad)?;
        Ok(BackupRestore { original, target: pfad, committed: false })
    }
    fn commit(mut self) {
        self.committed = true;
        // Drop läuft, aber tut nichts mehr.
    }
}

impl Drop for BackupRestore {
    fn drop(&mut self) {
        if !self.committed {
            // Bei Panic / Fehler: alten Inhalt wiederherstellen.
            let _ = std::fs::write(&self.target, &self.original);
        }
    }
}

Ein Datei-Schreib-Pattern: vor dem Schreiben Original sichern, dann schreiben. Bei Erfolg commit() — bei Fehler oder Panic wird der originale Inhalt durch Drop wiederhergestellt.

Logging-Span (tracing-Pattern)

Rust Tracing-Span
struct Span { name: &'static str, start: std::time::Instant }

impl Span {
    fn neu(name: &'static str) -> Self {
        println!(">>> {name}");
        Span { name, start: std::time::Instant::now() }
    }
}

impl Drop for Span {
    fn drop(&mut self) {
        let ms = self.start.elapsed().as_millis();
        println!("<<< {} ({}ms)", self.name, ms);
    }
}

fn berechnen() {
    let _s = Span::neu("berechnen");
    // ... Arbeit ...
    std::thread::sleep(std::time::Duration::from_millis(10));
}
// Beim Funktions-Ende: Span::drop läuft, gibt Latenz aus.

Ein Span misst die Dauer einer Operation per RAII. Ein Funktions-Body, der den Span anlegt, bekommt automatisch Start-Log, End-Log und Latenz-Messung — ohne expliziten Cleanup-Code.

Buffered Writer mit Auto-Flush

Rust BufWriter-Pattern
use std::io::{BufWriter, Write};
use std::fs::File;

fn schreibe_viel() -> std::io::Result<()> {
    let file = File::create("/tmp/big.log")?;
    let mut buf = BufWriter::new(file);

    for i in 0..1_000 {
        writeln!(buf, "Zeile {i}")?;
    }
    Ok(())
}
// buf wird hier gedroppt → noch nicht geschriebene Bytes werden geflusht.
// Dann wird file gedroppt → Handle geschlossen.

BufWriter puffert Schreibzugriffe und schreibt erst beim Flush wirklich raus. Sein Drop ruft flush() auf — du brauchst es nicht explizit zu rufen. Aber: wenn flush() im Drop einen Fehler liefert, wird er ignoriert. Für kritische Daten besser explizit buf.flush()? vor dem Scope-Ende.

Performance-Spike-Detector

Rust Threshold-Warning
struct SlowWarning {
    label: &'static str,
    start: std::time::Instant,
    threshold_ms: u128,
}

impl SlowWarning {
    fn neu(label: &'static str, ms: u128) -> Self {
        SlowWarning { label, start: std::time::Instant::now(), threshold_ms: ms }
    }
}

impl Drop for SlowWarning {
    fn drop(&mut self) {
        let elapsed = self.start.elapsed().as_millis();
        if elapsed > self.threshold_ms {
            eprintln!("SLOW: {} took {}ms (threshold {}ms)",
                      self.label, elapsed, self.threshold_ms);
        }
    }
}

fn arbeite() {
    let _w = SlowWarning::neu("arbeite", 50);
    // ... Arbeit ...
}

Pragmatisches Tooling: jede Funktion, die zu lange läuft, gibt eine Warnung aus — ohne dass die Funktion selbst Stopwatch-Code braucht.

FAQ

Wann sollte ich Drop selbst implementieren?

Nur wenn dein Typ eine Ressource verwaltet, die explizit freigegeben werden muss: File-Handle, Socket, GPU-Buffer, externe Library-Pointer, FFI-Allocations. Für gewöhnliche Daten (Felder mit String, Vec) brauchst du Drop nicht — die haben schon eigene Drops.

drop()-Methode kann ich nicht direkt aufrufen.

Der Compiler verbietet v.drop(). Workaround: std::mem::drop(v) — das ist eine Funktion, die v konsumiert und dann den Compiler den Drop einsetzen lässt. Selten gebraucht — meistens reicht das automatische Drop am Scope-Ende.

Funktioniert Drop auch bei panic?

Ja, im Standard-Profil (panic = "unwind"). Der Stack wird abgewickelt, alle lokalen Werte gedroppt. Bei panic = "abort" (in Cargo.toml) wird der Prozess sofort beendet — kein Drop läuft. Wichtig für Code, der Cleanup garantieren muss.

Drop und Copy sind unverträglich.

Ein Typ kann nicht gleichzeitig Copy UND Drop sein. Logisch: Copy heißt „kostenlos kopierbar", Drop heißt „braucht Cleanup". Beides zusammen würde bedeuten, dass eine Kopie ein zusätzliches Cleanup brauchen würde — Double-Free. Der Compiler verbietet das.

Drop kann keinen Fehler zurückgeben.

Die Methode hat Signatur fn drop(&mut self) — keine Result-Rückgabe, kein ?. Wer Cleanup mit möglichem Fehler braucht (z. B. „Commit der Datei darf fehlschlagen"), gibt eine eigene close()/commit()-Methode mit Result-Rückgabe — und nutzt Drop als „Fallback, der best-effort versucht".

mem::forget ist safe Rust.

Memory-Leaks verletzen keine der drei Ownership-Regeln — sie sind nicht im Sinne von Memory-Safety unsafe. Daher ist mem::forget ohne unsafe-Block aufrufbar. Trotzdem: außer bei FFI selten gewollt.

Drop läuft AUCH bei std::process::exit?

Nein. std::process::exit(code) beendet den Prozess ohne Drop-Aufrufe. Wer auf Drops für kritisches Cleanup angewiesen ist, sollte exit vermeiden — stattdessen aus main mit Ok(()) zurückkehren und Drops normal laufen lassen.

Drop-Order in Tupeln und Arrays.

Tupel-Elemente: in Deklarations-Reihenfolge gedroppt (links zuerst). Array-Elemente: ebenfalls (Index 0 zuerst). Struct-Felder: Deklarations-Reihenfolge. Bei manuellen RAII-Wrappers mit Abhängigkeiten zwischen Feldern: Reihenfolge im Struct bewusst wählen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Ownership

Zur Übersicht