Drop and Resource Management

Resource Acquisition and Cleanup

Resource acquisition and cleanup is the lifecycle discipline that ensures every finite system resource a program obtains—such as heap memory, open file descriptors, network sockets, database transactions, or synchronization locks—is deterministically released when the program no longer needs it. In Rust, resource lifetimes are tied directly to value ownership: acquiring a resource constructs an owning value, and destroying that value at the end of its scope releases the resource automatically.

Categories of System Resources

Every operating system enforces strict limits on non-memory resources as well as physical RAM. Failing to release any of these resources degrades or stalls the process:

Resource Type Standard Library Owner Underlying OS / Runtime Handle Consequence of Missing Cleanup
Heap allocation Box<T>, Vec<T>, String, Rc<T>, Arc<T> Virtual memory block from GlobalAlloc Memory growth and out-of-memory (OOM) abort
File descriptor std::fs::File POSIX fd (c_int) or Windows HANDLE EMFILE (Too many open files) error on new opens
Network socket TcpStream, TcpListener, UdpSocket Kernel socket descriptor + ephemeral port Port exhaustion and half-open remote connections
Thread synchronization lock MutexGuard<'_, T>, RwLockWriteGuard<'_, T> OS futex / pthread mutex state Permanent deadlock of waiting threads
Temporary filesystem path Custom guard (TempDir / TempFile) Directory or file entry on disk Disk space exhaustion and stale state

Comparing Resource Management Models Across Languages

Manual Management (C):
  acquire() -----> [early return?] -----> (Resource Leaked!)
             \---> use() --------------> release()

Tracing Garbage Collection (Java / Go / Python):
  acquire() -----> use() -----> [object unreachable] ... (unbounded delay) ... -> GC finalizer

Ownership-Bound Deterministic Cleanup (Rust):
  acquire() -----> use() -----> Scope Exit / Early `?` / Panic Unwind -> Drop runs immediately
Model Cleanup Timing Early-Return / Error Safety Runtime Overhead
Manual (malloc/free, open/close) Immediate if called on every path Error-prone; forgotten cleanup on error branches leaks resources Zero
Garbage Collection (finalize) Non-deterministic (if ever) Requires separate try-with-resources / defer for non-memory handles Tracing pause and memory overhead
Rust Ownership + Drop Deterministic at exact end of owner's scope Automatic across normal returns, ? propagation, break, and panic unwinds Zero runtime tracking overhead

Automatic Cleanup Across Control-Flow Branches

When a function exits early via return, break, or the ? error-propagation operator, the compiler automatically emits cleanup code (drop glue) for every initialized local variable in reverse declaration order before returning control to the caller.

struct Connection {
    id: u32,
}

impl Connection {
    fn open(id: u32) -> Self {
        println!("[+] opened connection {}", id);
        Self { id }
    }

    fn send(&self, payload: &str) -> Result<(), &'static str> {
        if payload.is_empty() {
            return Err("empty payload rejected");
        }
        println!("    connection {} sent {:?}", self.id, payload);
        Ok(())
    }
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("[-] closed connection {}", self.id);
    }
}

fn run_session(payload: &str) -> Result<(), &'static str> {
    let conn = Connection::open(1);
    conn.send(payload)?; // Early return on `Err` still drops `conn`!
    Ok(())
}

fn main() {
    println!("--- Session 1 (success) ---");
    let _ = run_session("PING");

    println!("--- Session 2 (early error return) ---");
    let res = run_session("");
    println!("result: {:?}", res);
}
--- Session 1 (success) ---
[+] opened connection 1
    connection 1 sent "PING"
[-] closed connection 1
--- Session 2 (early error return) ---
[+] opened connection 1
[-] closed connection 1
result: Err("empty payload rejected")