Rust Temporary Files
Temporary files are scratch files a program uses for a short job. The standard library can build these scratch files on its own.
Where scratch files live
temp_dir() asks the system. The answer is always an absolute path:
let dir = std::env::temp_dir();
println!("temp is absolute = {}", dir.is_absolute());A name nobody else picked
Put your process id in the name. No other running program has the same id:
let path = std::env::temp_dir().join(format!("mytool-{}.tmp", std::process::id()));
println!("in temp = {}", path.starts_with(std::env::temp_dir()));Create it only if it is free
create_new(true) refuses a taken name. It answers AlreadyExists instead of overwriting:
let first = OpenOptions::new().write(true).create_new(true).open("scratch.tmp");
println!("first try ok = {}", first.is_ok());
let second = OpenOptions::new().write(true).create_new(true).open("scratch.tmp");
println!("second try kind = {:?}", second.unwrap_err().kind());Write temp, then rename over
The rename swaps the finished file into place in one step:
fs::write("data.tmp", "new version")?;
fs::rename("data.tmp", "data.txt")?;
println!("{}", fs::read_to_string("data.txt")?);A scratch folder of your own
A whole folder works too. Build it, use it, then remove the whole tree:
let dir = Path::new("scratchpad");
fs::create_dir_all(dir)?;
fs::write(dir.join("a.txt"), "x")?;
println!("files inside = {}", fs::read_dir(dir)?.count());
fs::remove_dir_all(dir)?;
println!("gone = {}", !dir.exists());Example
use std::fs::{self, OpenOptions};
use std::io::Write;
fn main() {
// the old saved version
fs::write("config.txt", "version 1").expect("could not write the old config");
// save the new version beside it, then rename it over in one step
let tmp = "config.txt.tmp";
fs::write(tmp, "version 2").expect("could not write the temp file");
fs::rename(tmp, "config.txt").expect("could not rename over");
println!("saved = {}", fs::read_to_string("config.txt").expect("could not read back"));
println!("temp left = {}", std::path::Path::new(tmp).exists());
// a scratch file in the system temp folder, named with our process id
let scratch = std::env::temp_dir().join(format!("mytool-{}.tmp", std::process::id()));
let mut file = OpenOptions::new().write(true).create_new(true).open(&scratch)
.expect("could not create the scratch file");
write!(file, "scratch").expect("could not write the scratch file");
drop(file);
println!("scratch bytes = {}", fs::metadata(&scratch).expect("no metadata").len());
fs::remove_file(&scratch).expect("could not remove the scratch file");
println!("scratch cleaned = {}", !scratch.exists());
fs::remove_file("config.txt").expect("could not clean up");
}Key points
- A temporary file is scratch space that a program removes when the job is done.
- Use std::env::temp_dir() to find the folder the system keeps scratch files in
- Make the name unique before you create the file: your process id is a number no other running program shares
- Use OpenOptions with create_new(true) to create a file only when the name is still free.
- The call fails instead of replacing a stranger's file
- Write to a temp file first.
- Then rename it over the real file.
- A crash mid-write leaves the old real file untouched
- Clean up after yourself: remove the temp file or folder when the job is done
In this example, the program saves a config in the safe order. It writes version 2 to a temp name first. Then one rename swaps it over version 1. No half-written config ever exists. The second half makes a scratch file in the system temp folder. Its name carries the process id. create_new proves the name was free. The program writes seven bytes, measures them, and removes the file again.
Note
The tempfile crate automates all of this.
Its files delete themselves when dropped.
It also picks names that cannot collide.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why write to a temp file and rename, instead of writing the real file directly?