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:

  1. Write the new contents to a sibling temporary file in the same directory (so both paths reside on the same filesystem mount).
  2. Flush user-space buffers (.flush()?) and flush the OS kernel page cache to disk (.sync_all()?).
  3. 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}