Drop can be skipped
There are cases where destructors will not run.
#[derive(Debug)] struct OwnedFd(i32); impl Drop for OwnedFd { fn drop(&mut self) { println!("OwnedFd::drop() called with raw fd: {:?}", self.0); } } impl Drop for TmpFile { fn drop(&mut self) { println!("TmpFile::drop() called with owned fd: {:?}", self.0); // libc::unlink("/tmp/file") // panic!("TmpFile::drop() panics"); } } #[derive(Debug)] struct TmpFile(OwnedFd); impl TmpFile { fn open() -> Self { Self(OwnedFd(2)) } fn close(&self) { panic!("TmpFile::close(): not implemented yet"); } } fn main() { let owned_fd = OwnedFd(1); let file = TmpFile::open(); std::process::exit(0); // std::mem::forget(file); // file.close(); let _ = owned_fd; }
-
Drop is not guaranteed to always run. There is a number of cases when drop is skipped: the program can crash or exit, the value with the drop implementation can be leaked etc.
-
In the version that calls
std::process::exit,TmpFile::drop()is never run becauseexit()terminates the process immediately without any opportunity for adrop()method to be called.- You can prevent accidental use of
exitby denying theclippy::exitlint.
- You can prevent accidental use of
-
If you remove the
std::process::exit(0)line, eachdrop()method in this simple case will run in turn. -
Try uncommenting the
std::mem::forgetcall. What do you think will happen?mem::forget()takes ownership and “forgets” about the valuefilewithout running its destructorDrop::drop(). The destructor ofowned_fdis still run. -
Remove the
mem::forget()call, then uncomment thefile.close()call below it. What do you expect now?With the default
panic = "unwind"setting, the stack still unwinds and destructors run, even when the panic starts inmain.- With
panic = "abort"no destructors are run.
- With
-
As a last step, uncomment the
panic!insideTmpFile::drop()and run it. Ask the class: which destructors run before the abort?After a double panic, Rust no longer guarantees that remaining destructors will run:
- Some cleanup that was already in progress may still complete (for example, field destructors of the value currently being dropped),
- but anything scheduled later in the unwind path might be skipped entirely.
- This is why we say you cannot rely solely on
drop()for critical external cleanup, nor assume that a double panic aborts without running any further destructors.
-
Some languages forbid or restrict exceptions in destructors. Rust allows panicking in
Drop::drop, but it is almost never a good idea, since it can disrupt unwinding and lead to unpredictable cleanup. It is best avoided unless there is a very specific need, such as in the case of a drop bomb. -
Drop is suitable for cleaning up resources within the scope of a process, but it is not the right tool for providing hard guarantees that something happens outside of the process (e.g., on local disk, or in another service in a distributed system).
-
For example, deleting a temporary file in
drop()is fine in a toy example, but in a real program you would still need an external cleanup mechanism such as a temp file reaper. -
In contrast, we can rely on
drop()to unlock a mutex, since it is a process-local resource. Ifdrop()is skipped and the mutex is left locked, it has no lasting effects outside the process.