File System Operations
Writing Files
Rust supports three main patterns for writing files: one-shot whole-buffer writes (std::fs::write), buffered streaming writes (BufWriter::new(File::create(path)?)), and crash-safe atomic write-then-rename for critical configuration or state files.
Choosing the Right File Writing Strategy
| Writing Pattern | Code Idiom | Best Use Case |
|---|---|---|
| One-shot write | fs::write(path, bytes)? |
Writing an already-constructed String or Vec<u8> in a single call |
| Buffered streaming write | let mut w = BufWriter::new(File::create(path)?); |
Formatting many lines (writeln!) or serializing a large structure incrementally |
| Crash-safe atomic write | Write + sync_all() to path.with_extension("tmp"), then fs::rename(tmp, path)? |
Updating critical config files or databases so a power loss or crash mid-write never leaves a half-written corrupted file |
Crash-Safe Atomic File Replacement Pattern
If a process calls File::create("config.json") and crashes (or loses power) after writing half of the bytes, the original config.json is destroyed and only a truncated, unparseable file remains on disk!
To update a file atomically on POSIX and Windows filesystems:
- Write the new contents to a sibling temporary file in the same directory (so both paths reside on the same filesystem mount).
- Flush user-space buffers (
.flush()?) and flush the OS kernel page cache to disk (.sync_all()?). - Atomically replace the destination file using
std::fs::rename(&temp_path, &target_path)?.
Atomic Write-Sync-Rename Sequence:
1. Create `config.json.tmp` in same directory & write full payload
2. Call `tmp_file.sync_all()?` (Ensures all bytes are on physical disk)
3. Call `fs::rename("config.json.tmp", "config.json")?`
(Atomic POSIX directory-entry pointer swap: readers see either 100% old or 100% new!)
use std::fs::{self, File};
use std::io::{self, BufWriter, Write};
use std::path::Path;
pub fn write_file_atomically(target: &Path, contents: &[u8]) -> io::Result<()> {
let temp_path = target.with_extension("tmp_atomic");
{
let file = File::create(&temp_path)?;
let mut writer = BufWriter::new(file);
writer.write_all(contents)?;
// Unwrap the BufWriter (which flushes user-space RAM to the OS) and sync to disk:
let file = writer.into_inner()?;
file.sync_all()?;
}
// Atomically replace `target` with `temp_path`:
fs::rename(&temp_path, target)?;
Ok(())
}
fn main() -> io::Result<()> {
let target = std::env::temp_dir().join("rust_ch50_atomic_config.json");
write_file_atomically(&target, b"{\"version\":1}\n")?;
write_file_atomically(&target, b"{\"version\":2}\n")?;
let saved = fs::read_to_string(&target)?;
fs::remove_file(&target)?;
print!("atomically updated file: {}", saved);
Ok(())
}
atomically updated file: {"version":2}