Skip to content
Select theme

How to work with temporary files

Short answer: std::env::temp_dir() gives you the directory, OpenOptions::create_new(true) claims a name nobody else can take, and a Drop impl deletes it when you’re done. Drop-based cleanup dies with the process — after a crash the file stays — so sweep your old files at the next startup.

Start from a fresh project — cargo new imgtool, replace the contents of src/main.rs with the block below, and cargo run. The path it prints is this machine’s temp directory; yours will differ — macOS gives every user their own directory under /var/folders/…, Linux uses $TMPDIR or falls back to /tmp, and Windows goes through GetTempPath. Ask, don’t hard-code:

wrote /var/folders/9x/qv6cmc_x7qj8pbzj846pb4vc0000gn/T/demo-notes.txt

create_new(true) is the load-bearing line. The std docs for temp_dir warn that the directory “may be shared among users, or between processes with different privileges” and that a file with “a fixed or predictable name” invites an “insecure temporary file” vulnerability. create_new opens the file only if the name isn’t taken; when another process got there first, you get Os { code: 17, kind: AlreadyExists, message: "File exists" }. The remove_file above it is what makes reruns work — without it, the second run dies on create_new, the same race two instances of your program would hit.

A fixed name works for a demo and fails in production, where two instances run at once. The usual shape is prefix + process id + a nanosecond timestamp, with create_new arbitrating and a retry loop for the rare collision. Cleanup lives in Drop:

inside scope: true
after scope: false

When temp leaves scope the file is gone. A panic unwinds too — unwinding runs destructors.

Drop is a promise the process has to be alive to keep. std::process::exit never returns, and its docs say plainly that “no destructors on the current stack or any other thread’s stack will be run.” A crash is the same hole — abort’s docs list what gets skipped (“no destructors … will be run, Rust IO buffers … will not be flushed”) — and kill -9 doesn’t run your code at all. Watch it happen:

Terminal window
$ cargo run -q
created /var/folders/9x/qv6cmc_x7qj8pbzj846pb4vc0000gn/T/imgtool-22348-1792538486639309000
$ ls /var/folders/9x/qv6cmc_x7qj8pbzj846pb4vc0000gn/T/imgtool-22348-1792538486639309000
/var/folders/9x/qv6cmc_x7qj8pbzj846pb4vc0000gn/T/imgtool-22348-1792538486639309000

Exit code 0, file still on disk. The numbers in the name are the process id and a nanosecond timestamp, so yours will differ.

Cleanup that survives a crash can’t live in the crashing process — someone else has to do it. The cheap version is a sweep at the next startup: give every file a prefix you own, delete the stale ones before creating new ones.

stale-demo gone: true

The block plants a fake leftover two hours old (set_modified backdates the timestamp), then sweeps anything with the imgtool- prefix older than an hour. The age cutoff matters: a file a minute old probably belongs to an instance that’s still running. Removal failures are ignored on purpose — the file may be gone already, and a sweeper that dies over a lost race isn’t much of a sweeper.

If you’d rather not hand-roll this, tempfile is the crate the temp_dir docs point to: NamedTempFile::new() creates a uniquely named file in temp_dir(), TempDir::new() a whole directory, both deleted when the value drops, keep() if you want one to stay (checked against tempfile 3.27.0). The crash hole is the same — a TempDir held across process::exit is left behind — so the sweep stays your job.