Drop and Resource Management

Drop Order

Drop order is the deterministic sequence in which the Rust compiler destroys values at the end of a scope, expression, or composite type. Because later variables often borrow from or depend on earlier variables, knowing the exact drop order prevents lifetime errors and deadlocks.

Summary of Drop Order Rules

Construct Drop Order Reason / Rule
Local variables (let a; let b;) Reverse declaration order (b, then a) Later variables may hold references or logical dependencies on earlier variables
Struct fields (struct S { x, y }) Declaration order (S::drop, then x, then y) Top-to-bottom source field order after the enclosing struct's own Drop::drop finishes
Enum variant fields (Variant(x, y)) Declaration order (Enum::drop, then x, then y) Top-to-bottom / left-to-right order of the active variant's fields
Tuple elements ((a, b, c)) First-to-last order (a, then b, then c) Left-to-right element index order (0, 1, 2)
Array and slice elements ([a, b, c]) First-to-last order ([0], then [1], then [2]) Contiguous forward index order 0..len
Function arguments (fn f(a: T, b: T)) Reverse parameter order (b, then a) Parameters behave like local bindings declared left-to-right
Closure captures Unspecified field order after closure drops Wrap order-sensitive captures in a struct if destruction order matters
Block Scope (LIFO — Reverse Order):          Struct Fields (FIFO — Declaration Order):
{                                            struct Pipeline {
    let first = ...;   // Dropped 3rd            stage_1: Trace, // Dropped 1st (after Pipeline::drop)
    let second = ...;  // Dropped 2nd            stage_2: Trace, // Dropped 2nd
    let third = ...;   // Dropped 1st            stage_3: Trace, // Dropped 3rd
}                                            }

Demonstrating Block vs. Struct vs. Tuple Drop Order

struct Trace(&'static str);

impl Drop for Trace {
    fn drop(&mut self) {
        println!("drop: {}", self.0);
    }
}

struct PairStruct {
    first: Trace,
    second: Trace,
}

impl Drop for PairStruct {
    fn drop(&mut self) {
        println!("drop: PairStruct::drop()");
    }
}

fn main() {
    println!("=== 1. Local bindings (reverse order) ===");
    {
        let _a = Trace("local a");
        let _b = Trace("local b");
        let _c = Trace("local c");
    }

    println!("=== 2. Struct fields (declaration order) ===");
    {
        let _s = PairStruct {
            first: Trace("field first"),
            second: Trace("field second"),
        };
    }

    println!("=== 3. Tuple and array elements (index order) ===");
    {
        let _tuple = (Trace("tuple.0"), Trace("tuple.1"));
        let _array = [Trace("array[0]"), Trace("array[1]")];
    }
}
=== 1. Local bindings (reverse order) ===
drop: local c
drop: local b
drop: local a
=== 2. Struct fields (declaration order) ===
drop: PairStruct::drop()
drop: field first
drop: field second
=== 3. Tuple and array elements (index order) ===
drop: array[0]
drop: array[1]
drop: tuple.0
drop: tuple.1

Why Struct Field Order Matters for Dependent Handles

Because struct fields drop from first declared to last declared, any field that must be destroyed before an underlying context or backing resource is destroyed must be declared earlier (higher up) in the struct definition:

struct Statement {
    sql: &'static str,
}

impl Drop for Statement {
    fn drop(&mut self) {
        println!("1. finalizing statement {:?} while db handle is still open", self.sql);
    }
}

struct Database {
    url: &'static str,
}

impl Drop for Database {
    fn drop(&mut self) {
        println!("2. closing database connection {:?}", self.url);
    }
}

struct QueryRunner {
    // Declared first -> dropped FIRST (while `db` is still open):
    active_stmt: Statement,
    // Declared second -> dropped SECOND:
    db: Database,
}

fn main() {
    let _runner = QueryRunner {
        active_stmt: Statement {
            sql: "SELECT * FROM users",
        },
        db: Database {
            url: "postgres://localhost/app",
        },
    };
}
1. finalizing statement "SELECT * FROM users" while db handle is still open
2. closing database connection "postgres://localhost/app"