---
title: How to Handle Resource Cleanup in Rust (RAII)
summary: Rust uses the Drop trait to automatically clean up resources when variables go out of scope, ensuring safe memory management without manual intervention.
lang: en
slug: how-to-handle-resource-cleanup-in-rust-raii
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-handle-resource-cleanup-in-rust-raii/
---

# How to Handle Resource Cleanup in Rust (RAII)



## The cleanup that writes itself

You write a function that opens a database connection. You run a query. You close the connection. You add error handling. You add a retry loop. You add a timeout. Now you have five places where the connection must close. You miss one. The connection leaks. The database runs out of slots. Your service crashes.

In Rust, you don't scatter cleanup code across five branches. You write cleanup once. You attach cleanup to the connection object. When the object vanishes, the cleanup runs. This pattern is RAII. Rust builds it into the language via the `Drop` trait. You never write manual cleanup calls. The compiler guarantees resources release exactly when they should.

## RAII and the Drop trait

RAII stands for Resource Acquisition Is Initialization. The name comes from C++. The idea is universal. Acquiring a resource and setting up its cleanup happen at the same time. Creating the value acquires the resource. Destroying the value releases it.

No `try/finally` blocks. No `with` statements. No manual `close()` calls. The value itself carries the cleanup logic. When the value goes out of scope, the cleanup runs. If the value moves, the cleanup moves with it. If the value clones, both copies clean up independently.

Think of a rental car with a built-in return mechanism. You pick up the car. The rental company tracks it. You don't drive it back to the lot. You just stop using it. The moment you walk away, the car teleports back to the depot. You can't forget to return it. The return is tied to your possession.

Rust implements this with the `Drop` trait. Any type can implement `Drop`. The trait has one method: `fn drop(&mut self)`. You never call this method. The compiler generates calls automatically. When a value is about to be destroyed, the compiler inserts a call to `Drop::drop`.

## Minimal example

Here is a type that implements `Drop`. The cleanup runs when the value leaves scope.

```rust
/// A log file handle that prints a message when closed.
struct LogFile {
    path: String,
}

impl Drop for LogFile {
    /// Runs automatically when the LogFile is destroyed.
    fn drop(&mut self) {
        println!("Closing log: {}", self.path);
    }
}

fn main() {
    // LogFile is created. Resource acquired.
    let log = LogFile {
        path: "app.log".to_string(),
    };

    // log is used here.
    println!("Writing to {}", log.path);

    // log goes out of scope. Drop runs automatically.
    // Output: Closing log: app.log
}
```

The `Drop` implementation defines what happens on cleanup. The `main` function creates a `LogFile`. The value lives until the end of the block. When the block ends, the compiler calls `Drop::drop(&mut log)`. The message prints. The memory frees.

## How the compiler inserts cleanup

The compiler tracks every value. It knows when a value is created. It knows when a value is last used. It inserts drop calls at the last use. This is called "drop glue".

If you move a value, the drop moves with it. The original location doesn't drop. The new location drops. This ties cleanup to ownership.

```rust
fn main() {
    let log = LogFile {
        path: "app.log".to_string(),
    };

    // Move log into a function.
    // The drop moves with the value.
    process_log(log);

    // log is gone. No drop here.
}

/// Takes ownership of the log file.
fn process_log(log: LogFile) {
    // log lives here now.
    println!("Processing {}", log.path);
    // log drops here when the function returns.
}
```

Moving suppresses the drop at the source. This is why `Vec` doesn't leak when you move it. The buffer follows the data. The drop runs at the new location.

Ah-ha reveal: `std::mem::drop` is just a function that takes ownership and does nothing. The function body is empty. The act of passing ownership to the function forces the drop. This is a clever trick. You use `std::mem::drop` to force cleanup before the end of the scope.

```rust
use std::mem;

fn main() {
    let log = LogFile {
        path: "app.log".to_string(),
    };

    // Force drop now.
    mem::drop(log);

    // log is gone. Cannot use it here.
    // println!("{}", log.path); // Error: use of moved value
}
```

Trust the drop order. The compiler knows what depends on what.

## The guard pattern

Libraries use RAII to manage locks, file handles, and other resources. The standard pattern is a "guard" struct. You acquire the resource. You get a guard. The guard holds the resource. The guard implements `Drop`. `Drop` releases the resource.

This guarantees release even if you panic. If the code panics, the guard still drops. The resource still releases. No deadlocks. No leaks.

```rust
use std::sync::Mutex;

fn main() {
    let mutex = Mutex::new(0);

    {
        // lock() returns a MutexGuard.
        // The guard holds the lock.
        let guard = mutex.lock().unwrap();

        // Do work while locked.
        *guard += 1;

        // guard drops here.
        // Drop unlocks the mutex automatically.
    }

    // Mutex is unlocked now.
    println!("Value: {}", *mutex.lock().unwrap());
}
```

`MutexGuard` is a guard struct. `lock()` returns the guard. The guard holds the lock. `Drop` unlocks. This pattern appears everywhere in Rust. `File` handles use it. `RefCell` borrows use it. Database connections use it.

Convention aside: Types that manage resources often end in `Guard`, `Lock`, or `Handle`. This signals RAII behavior. If you see `MutexGuard`, you know it drops and unlocks. If you see `File`, you know it drops and closes.

Trust the guard pattern. It turns a potential deadlock into a guaranteed unlock.

## Realistic example: Custom resource

Here is a realistic example. A type that manages a simulated network connection. The connection acquires a socket. The connection drops and closes the socket.

```rust
/// A network connection that closes automatically.
struct Connection {
    id: u32,
}

impl Connection {
    /// Creates a new connection.
    fn new(id: u32) -> Self {
        println!("Connecting to socket {}", id);
        Connection { id }
    }
}

impl Drop for Connection {
    /// Closes the connection when the value is destroyed.
    fn drop(&mut self) {
        println!("Closing socket {}", self.id);
    }
}

fn main() {
    let conn = Connection::new(42);

    // Use the connection.
    println!("Sending data on {}", conn.id);

    // conn drops here. Socket closes.
}
```

The `Connection` struct holds the resource identifier. `new` acquires the resource. `Drop` releases it. The user never calls `close`. The cleanup is automatic.

## Pitfalls and edge cases

Drop has rules. Breaking them causes errors or aborts.

**Manual drop is forbidden.** You cannot call `drop` manually. `value.drop()` is a compile error. The compiler prevents double drops. If you need early drop, use `std::mem::drop(value)`. This takes ownership and forces the drop.

**Drop panics abort the program.** If `Drop` panics, the runtime calls `abort`. No unwinding. This prevents double panic. If `Drop` panics during unwinding, the second panic would be catastrophic. Abort is safer. Never panic in `Drop`. Handle errors gracefully.

**Drop order is reverse declaration.** Values drop in reverse order of declaration. Later values drop first. This handles dependencies. If `a` depends on `b`, `a` drops before `b`. The compiler enforces this.

```rust
fn main() {
    let a = LogFile { path: "a.log".to_string() };
    let b = LogFile { path: "b.log".to_string() };
    // b drops first. Then a drops.
    // Output:
    // Closing log: b.log
    // Closing log: a.log
}
```

Don't fight the drop order. Structure your data so dependencies are clear.

**ManuallyDrop suppresses drop.** `std::mem::ManuallyDrop<T>` wraps a value and suppresses drop. You must call `ManuallyDrop::into_inner` or `forget` to clean up. Use this only for custom allocators or FFI where you manage memory manually.

```rust
use std::mem::ManuallyDrop;

fn main() {
    let log = ManuallyDrop::new(LogFile {
        path: "app.log".to_string(),
    });

    // log does not drop here.
    // You must clean up manually.
    ManuallyDrop::into_inner(log);
    // Now it drops.
}
```

Use `ManuallyDrop` only when you have a proof of cleanup. Otherwise, let the compiler handle it.

## Decision matrix

Use `Drop` when you need automatic cleanup tied to a value's lifetime. Use `Drop` for file handles, mutex locks, database connections, and any resource that must release when the value dies. Use `std::mem::drop` when you need to force cleanup before the end of the scope. Use `std::mem::drop` to release a lock early or free memory before a long computation. Use `ManuallyDrop` when you are managing memory manually or passing ownership to FFI and will handle cleanup yourself. Use `ManuallyDrop` for custom allocators or when interfacing with C code that takes ownership. Use a guard struct when you need to lock a resource and guarantee unlock, like a mutex or file handle. Use the guard pattern for any resource that requires a paired acquire and release operation.

Treat the `Drop` implementation as a promise. If you can't keep the promise, use `ManuallyDrop`.

## Where to go next

- [How to Use Arena Allocation in Rust](/en/how-to-use-arena-allocation-in-rust/)
- [What is the difference between move and copy](/en/what-is-the-difference-between-move-and-copy/)
- [How does ownership work in Rust](/en/how-does-ownership-work-in-rust/)