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):
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.
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: APIDie 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
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
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
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.
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
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:
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:
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: aDas 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:
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:
# 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.
# 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
unsafenö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.
# 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 nehmenDer 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
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
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
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
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
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)
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
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
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
- std::ops::Drop
- std::mem::drop
- std::mem::forget
- The Rust Book – Drop
- Rust Reference – Destructors
- The Rustonomicon – Drop Check