Smart Pointers

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.