Drop: Option
struct File(Option<Handle>); impl File { fn open(path: &'static str) -> std::io::Result<Self> { Ok(Self(Some(Handle { path }))) } fn write(&mut self, data: &str) -> std::io::Result<()> { match &mut self.0 { Some(handle) => println!("write '{data}' to file '{}'", handle.path), None => unreachable!(), } Ok(()) } fn close(mut self) -> std::io::Result<&'static str> { Ok(self.0.take().unwrap().path) } } impl Drop for File { fn drop(&mut self) { if let Some(handle) = self.0.take() { println!("automatically closing handle for file: {}", handle.path); } } } struct Handle { path: &'static str, } impl Drop for Handle { fn drop(&mut self) { println!("closed handle for file: {}", self.path) } } fn main() -> std::io::Result<()> { let mut file = File::open("foo.txt")?; file.write("hello")?; println!("manually closed file: {}", file.close()?); Ok(()) }
-
In this example we want to let the user call
close()manually so that errors from closing the file can be reported explicitly. -
At the same time we still want RAII semantics: if the user forgets to call
close(), the handle must be cleaned up automatically inDrop. -
Wrapping the handle in an
Optiongives us both behaviors.close()extracts the handle withtake(), andDroponly runs cleanup if a handle is still present.Demo: remove the
.close()call and run the code —Dropnow prints the automatic cleanup. -
The main downside is ergonomics.
Optionforces us to handle both theSomeandNonecase even in places where, logically,Nonecannot occur. Rust’s type system cannot express that relationship betweenFileand itsHandle, so we handle both cases manually.
More to explore
Instead of Option we could use
ManuallyDrop,
which suppresses automatic destruction by preventing Rust from calling Drop
for the value; you must handle teardown yourself.
The scopeguard example on the previous slide shows how
ManuallyDrop can replace Option to avoid handling None in places where the
value should always exist.
In such designs we typically track the drop state with a separate flag next to
the ManuallyDrop<Handle>, which lets us track whether the handle has already
been manually consumed.