Drop and Resource Management

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.