Implementing Drop
Implementing Drop for a custom type defines the exact actions that run when an instance of that type is destroyed. A Drop implementation must match the exact trait bounds declared on the type itself and must leave all fields in a valid state so the compiler's automatic field-level drop glue can run afterward.
Trait Bound Rules on impl Drop (E0367)
An impl Drop for MyType<...> block must have the exact same generic parameters and where bounds as the struct or enum definition itself. Specializing Drop for a subset of type parameters (for example, impl<T: Display> Drop for Wrapper<T> when struct Wrapper<T> does not require T: Display) is rejected with compiler error E0367:
// ❌ does not compile
use std::fmt::Display;
struct Wrapper<T> {
value: T,
}
impl<T: Display> Drop for Wrapper<T> {
fn drop(&mut self) {
println!("dropping {}", self.value);
}
}
fn main() {}
error[E0367]: `Drop` impl requires `T: std::fmt::Display` but the struct it is implemented for does not
--> src/main.rs:8:8
|
8 | impl<T: Display> Drop for Wrapper<T> {
| ^^^^^^^^
|
note: the implementor must specify the same requirement
--> src/main.rs:4:1
|
4 | struct Wrapper<T> {
| ^^^^^^^^^^^^^^^^^
To use a trait method inside Drop::drop, declare the bound on the struct definition (struct Wrapper<T: Display>).
Taking Ownership of a Field Inside Drop::drop
Because Drop::drop receives &mut self (rather than self by value) and the compiler runs drop glue on every field after drop(&mut self) returns, you cannot move a non-Copy field directly out of self using let f = self.field;.
When cleanup requires consuming a field by value (for example, joining a std::thread::JoinHandle<()>), wrap that field in Option<T> and call .take() inside drop(&mut self), or wrap it in std::mem::ManuallyDrop<T> and call ManuallyDrop::take:
use std::thread::{self, JoinHandle};
struct BackgroundWorker {
name: String,
handle: Option<JoinHandle<()>>,
}
impl BackgroundWorker {
fn spawn(name: &str) -> Self {
let worker_name = name.to_string();
let handle = thread::spawn(move || {
println!(" [worker {:?}] finished background task", worker_name);
});
Self {
name: name.to_string(),
handle: Some(handle),
}
}
}
impl Drop for BackgroundWorker {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
println!("joining worker {:?} inside Drop...", self.name);
let _ = handle.join();
}
}
}
fn main() {
let _worker = BackgroundWorker::spawn("indexer");
println!("main scope ending");
}
main scope ending
joining worker "indexer" inside Drop...
[worker "indexer"] finished background task
Partial Moves Are Forbidden on Types Implementing Drop (E0509)
If a struct does not implement Drop, Rust allows moving individual fields out of the struct (partial move) and drops only the remaining unmoved fields. Once a struct implements Drop, moving a single field out of an instance is forbidden (E0509) because Drop::drop(&mut self) expects the entire struct—and all of its fields—to be fully initialized when the destructor runs:
// ❌ does not compile
struct TrackedConfig {
name: String,
port: u16,
}
impl Drop for TrackedConfig {
fn drop(&mut self) {
println!("dropping config for {}", self.name);
}
}
fn main() {
let cfg = TrackedConfig {
name: String::from("api"),
port: 8080,
};
let extracted_name = cfg.name;
println!("{}:{}", extracted_name, cfg.port);
}
error[E0509]: cannot move out of type `TrackedConfig`, which implements the `Drop` trait
--> src/main.rs:17:26
|
17 | let extracted_name = cfg.name;
| ^^^^^^^^ cannot move out of here
|
= note: move occurs because `cfg.name` has type `String`, which does not implement the `Copy` trait
help: consider borrowing here
|
17 | let extracted_name = &cfg.name;
| +
Borrowing &cfg.name, cloning cfg.name.clone(), or replacing the field with std::mem::take(&mut cfg.name) preserves full initialization so Drop::drop can execute safely.