Drop and Resource Cleanup
The std::ops::Drop trait defines deterministic resource cleanup logic that the compiler executes automatically whenever a value goes out of scope, is overwritten, or is unwound during a panic. Every smart pointer relies on Drop to deallocate heap memory, decrement reference counts, or release synchronization locks (RAII — Resource Acquisition Is Initialization).
The Drop Trait Signature
pub trait Drop {
fn drop(&mut self);
}
When a variable reaches the end of its lifetime, the compiler inserts a call to Drop::drop(&mut val), followed recursively by dropping each of the struct's fields in declaration order.
Deterministic Drop Order Rules
Rust guarantees the exact order in which values are dropped:
| Scope / Construct | Guaranteed Drop Order |
|---|---|
Local variables in a block { let a; let b; } |
Reverse declaration order (LIFO): b is dropped first, then a |
Fields of a struct or enum variant |
Source declaration order (top-to-bottom): first field is dropped first, after the enclosing struct's own Drop::drop finishes |
Elements of a tuple (a, b, c) or array [a, b, c] |
First-to-last order: index 0 is dropped first, then 1, then 2 |
| Temporary values in a statement | Dropped at the end of the enclosing statement (unless lifetime-extended into a reference binding) |
struct Guard(&'static str);
impl Drop for Guard {
fn drop(&mut self) {
println!("dropping {}", self.0);
}
}
struct Container {
first_field: Guard,
second_field: Guard,
}
impl Drop for Container {
fn drop(&mut self) {
println!("entering Container::drop");
}
}
fn main() {
let _local_1 = Guard("local_1 (declared 1st, dropped last)");
let _container = Container {
first_field: Guard("first_field (dropped 1st after Container::drop)"),
second_field: Guard("second_field (dropped 2nd after Container::drop)"),
};
let _local_2 = Guard("local_2 (declared last, dropped 1st)");
println!("--- reaching end of main ---");
}
--- reaching end of main ---
dropping local_2 (declared last, dropped 1st)
entering Container::drop
dropping first_field (dropped 1st after Container::drop)
dropping second_field (dropped 2nd after Container::drop)
dropping local_1 (declared 1st, dropped last)
Early Cleanup with std::mem::drop
Calling val.drop() directly is forbidden by the compiler (E0040) because the compiler would also call drop(&mut val) automatically at the end of the scope, causing a double free:
// ❌ does not compile
struct Handle;
impl Drop for Handle {
fn drop(&mut self) {}
}
fn main() {
let mut h = Handle;
h.drop();
}
error[E0040]: explicit use of destructor method
--> src/main.rs:10:7
|
10 | h.drop();
| ^^^^ explicit destructor calls not allowed
|
help: consider using `drop` function
|
10 | drop(h);
| +++++ ~
To release a smart pointer or lock guard before the end of a lexical block, pass ownership to std::mem::drop(val) (which is in the prelude as drop(val) and implemented as pub fn drop<T>(_x: T) {}).
Suppressing Drop with ManuallyDrop<T> and mem::forget
When writing custom smart pointers or transferring ownership across FFI, std::mem::ManuallyDrop<T> (a zero-cost #[repr(transparent)] wrapper) or std::mem::forget(val) suppresses automatic destructor execution so you can control when or whether cleanup occurs.