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(()) }
-
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 theDroptrait to release the resource automatically. This ties cleanup to the lifetime of theFilevalue.#![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 aResult. Any failures must be handled internally or ignored. In the standard library, errors during FD closure insideDropare silently discarded. See the implementation: https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196 -
When is
Drop::dropcalled?Normally, when the
filevariable inmain()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 inmain.In contrast, C++ runs destructors in the original scope even for moved-from values.
-
Demo: insert
panic!("oops")at the start ofread_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.
- Learn more: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html
- There is an experimental
AsyncDroptrait available on nightly: https://doc.rust-lang.org/nightly/std/future/trait.AsyncDrop.html