273. remove_file — Deleting Something That Might Already Be Gone
create_dir_all (bite 272) is idempotent by design. Its deletion twins are not: remove_file and remove_dir_all fail with NotFound — and the exists() guard is the same race all over again.
Cleanup code usually doesn’t care whether the thing was there:
| |
So people reach for the same guard the whole filesystem run has been warning about:
| |
Check-then-act again (bite 270): if anything else deletes the file between exists() and remove_file, you’re back to the NotFound error the guard was supposed to prevent.
The fix is to call the operation unconditionally and treat “already gone” as success:
| |
Now it’s safe to call on every shutdown, every retry, every run — the outcome you wanted (“the file is not there”) is the same whether the call deleted it or found nothing.
Three details worth knowing:
- Same pattern for directories.
fs::remove_dir_allalso errors on a missing path; wrap it the same way. (Since Rust 1.62 its internals are TOCTOU-hardened on most platforms, so the recursive walk doesn’t race — but the top-levelNotFoundis still yours to handle.) - Only swallow
NotFound. APermissionDeniedor “directory not empty” is a real failure — thematchabove still propagates everything else, which a barelet _ = fs::remove_file(...)would silently eat. - Deletion may not be immediate. On Windows, a file open by another process is marked for deletion and disappears when the last handle closes — so don’t follow a successful
remove_filewith code that assumes the name is instantly reusable.
The asymmetry is worth remembering: create_dir_all bakes idempotence in, the remove_* family makes you opt in with one ErrorKind match.