RAII: Drop trait

RAII (Resource Acquisition Is Initialization) ties the lifetime of a resource to the lifetime of a value.

Rust uses RAII to manage memory, and the Drop trait allows you to extend this to other resources, such as file descriptors or locks.

pub struct File(std::os::fd::RawFd);

impl File {
    pub fn open(path: &str) -> Result<Self, std::io::Error> {
        // [...]
        Ok(Self(0))
    }

    pub fn read_to_end(&mut self) -> Result<Vec<u8>, std::io::Error> {
        // [...]
        Ok(b"example".to_vec())
    }

    pub fn close(self) -> Result<(), std::io::Error> {
        // [...]
        Ok(())
    }
}

fn main() -> Result<(), std::io::Error> {
    let mut file = File::open("example.txt")?;
    println!("content: {:?}", file.read_to_end()?);
    Ok(())
}
This slide and its sub-slides should take about 110 minutes.
  • Easy to miss: file.close() is never called. Ask the class if they noticed.

  • To release the file descriptor correctly, file.close() must be called after the last use — and also in early-return paths in case of errors.

  • Instead of relying on the user to call close(), we can implement the Drop trait to release the resource automatically. This ties cleanup to the lifetime of the File value.

    #![allow(unused)]
    fn main() {
    impl Drop for File {
        fn drop(&mut self) {
            // libc::close(...);
            println!("file descriptor was closed");
        }
    }
    }
  • Note that Drop::drop() cannot return a Result. Any failures must be handled internally or ignored. In the standard library, errors during FD closure inside Drop are silently discarded. See the implementation: https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196

  • When is Drop::drop called?

    Normally, when the file variable in main() goes out of scope (either on return or due to a panic), drop() is called automatically.

    If the file is moved into another function (as is this case with File::close()), the value is dropped when that function returns — not in main.

    In contrast, C++ runs destructors in the original scope even for moved-from values.

  • Demo: insert panic!("oops") at the start of read_to_end() and run it. drop() still runs during unwinding.

More to Explore

The Drop trait has another important limitation: it is not async.

This means you cannot await inside a destructor, which is often needed when cleaning up asynchronous resources like sockets, database connections, or tasks that must signal completion to another system.