Skip to content

v0.9.3 — mod-tempdir

Pre-release
Pre-release

Choose a tag to compare

@jamesgober jamesgober released this 13 May 23:31
· 4 commits to main since this release

mod-tempdir v0.9.3 — Release Notes

Date: 2026-05-13
Compare: v0.9.2...v0.9.3

Headline

Crash-safe file finalization via NamedTempFile::persist_atomic,
with a structured error type that preserves the source temp file
on failure so a retry path never loses data.

What's new

Public API additions

  • NamedTempFile::persist_atomic(target) -> Result<PathBuf, PersistAtomicError>
    Atomically move the temp file to target and disable cleanup on
    drop. Performs the canonical "atomic durable write" sequence:

    1. fsync the temp file
      (std::fs::File::sync_all)
    2. atomic std::fs::rename (POSIX rename(2) on Unix,
      MoveFileExW with MOVEFILE_REPLACE_EXISTING on Windows)
    3. best-effort fsync of the target's parent directory so the
      rename itself survives a crash.

    Atomic within a single filesystem. Cross-filesystem target
    returns EXDEV (Unix) or the equivalent (Windows) inside the
    error.

  • PersistAtomicError { error: io::Error, file: NamedTempFile }
    Structured error type. On any failure of persist_atomic, the
    temp file is preserved on disk and the original NamedTempFile
    is returned to the caller inside the error so a retry or
    fallback path does not lose the source. Implements Debug,
    Display, std::error::Error, and From<PersistAtomicError> for io::Error for callers that only need the underlying IO
    error.

Test additions

tests/persist_atomic.rs — four integration tests:

  1. Move + content preservation on the same filesystem.
  2. Replacement of an existing target.
  3. Data-integrity error path: target's parent directory missing
    → source survives, recovered NamedTempFile points at the
    original temp path.
  4. Post-success invariant: nothing remains at the original temp
    path after a successful persist.

Documentation

  • README gets a new "Atomic persistence" section with the retry
    pattern.
  • REPS.md §3 lists persist_atomic and PersistAtomicError in
    the public API surface.
  • Rustdoc on persist_atomic includes both the happy-path example
    and the retry pattern on recoverable error.

Why no fsys

The roadmap reserved a possible v0.9.3+ fsys integration for
this milestone. After auditing the fsys public API, the
integration was not taken:

  • fsys::platform::atomic_rename is pub(crate) — not callable
    from outside the crate.
  • fsys::Handle::rename requires both paths to live under a
    single handle root, which does not fit a generic
    temp_dir → arbitrary_target move.
  • std::fs::rename invokes the same OS primitives fsys uses
    internally (POSIX rename(2) on Unix, MoveFileExW on
    Windows), so the std-only path is functionally equivalent for
    this use case and keeps the default zero-dep build intact.

Same architectural call as the retired v0.9.1 fsys-for-directory-ops
milestone: when fsys's value lives in its internals rather than
its public surface, adding the dep does not pay off.

Migration

Purely additive. No code edits needed for existing callers. To
use the new method:

use mod_tempdir::NamedTempFile;
use std::io::Write;

let f = NamedTempFile::new()?;
{
    let mut h = std::fs::OpenOptions::new().write(true).open(f.path())?;
    h.write_all(b"finalized payload")?;
}
match f.persist_atomic("config.toml") {
    Ok(landed) => { /* `landed` is the target path */ }
    Err(e) => {
        // `e.file` is the original NamedTempFile, intact for retry.
        // `e.error` is the underlying io::Error.
    }
}
# Ok::<(), std::io::Error>(())

Verification

  • cargo build / --features mod-rand / --all-features
  • cargo +1.75 build --all-features (MSRV check)
  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features: 63 tests pass (Windows)
  • cargo test (default): 59 tests pass
  • cargo doc --no-deps and --all-features: 0 warnings
  • Banned-word scan and em-dash scan: 0 hits across shipping files.

CI matrix: ubuntu-latest, macos-latest, windows-latest.

Limitations

  • persist_atomic is atomic only within a single filesystem.
    Cross-mount target returns EXDEV / equivalent. Callers
    needing cross-filesystem finalization should copy through the
    target filesystem first using TempDir::with_prefix rooted at
    the target's parent.
  • Parent-directory fsync is best-effort; on Windows it requires
    FILE_FLAG_BACKUP_SEMANTICS to acquire the directory handle.
    Failures here are silent and do not affect the return value
    (the rename itself has already succeeded by that point).
  • The data-integrity contract (source preserved on failure) means
    a successful persist_atomic consumes self; an unsuccessful
    one returns it inside PersistAtomicError.file. Callers that
    use io::Result<PathBuf> via the From conversion lose the
    recovered file. Use the structured Result<PathBuf, PersistAtomicError>
    return type directly when retry-on-failure matters.