What is the difference between tempfile::tempfile and tempfile::tempdir for temporary resource management?

tempfile::tempfile creates an anonymous temporary file in the system's temporary directory that is automatically deleted when the file handle is closed, while tempfile::tempdir creates a temporary directory with a persistent path that is automatically deleted (along with its contents) when the TempDir guard is dropped. The key distinction is scope and accessibility: tempfile() returns a File handle without exposing a filesystem path, making it ideal for scratch data that never needs to be accessed by other processes, while tempdir() returns a TempDir guard that provides a PathBuf to the directory, enabling you to create multiple files within it and share paths with external tools. Both leverage RAII for automatic cleanup, but tempfile offers stronger deletion guarantees through OS-level support on most platforms, whereas tempdir cleanup depends on the destructor running successfully.

Basic tempfile Usage

use tempfile::tempfile;
use std::io::{Write, Read, Seek, SeekFrom};
 
fn main() -> std::io::Result<()> {
    // tempfile() creates an anonymous temporary file
    // No filesystem path is exposed
    let mut file = tempfile()?;
    
    // Write to the file
    writeln!(file, "Hello, temporary file!")?;
    
    // Seek back to read
    file.seek(SeekFrom::Start(0))?;
    
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    
    println!("Contents: {}", contents);
    
    // File is automatically deleted when `file` goes out of scope
    // On Unix, it's already unlinked from filesystem
    // On Windows, it's deleted when the handle is closed
    
    Ok(())
}

tempfile() creates an unnamed file that's automatically cleaned up when closed.

Basic tempdir Usage

use tempfile::tempdir;
use std::fs::File;
use std::io::Write;
use std::path::Path;
 
fn main() -> std::io::Result<()> {
    // tempdir() creates a temporary directory
    // The directory path is accessible
    let dir = tempdir()?;
    
    println!("Temporary directory: {:?}", dir.path());
    
    // Create files inside the directory
    let file_path = dir.path().join("data.txt");
    let mut file = File::create(&file_path)?;
    writeln!(file, "Hello from tempdir!")?;
    
    // Can create multiple files
    let config_path = dir.path().join("config.json");
    let mut config = File::create(&config_path)?;
    writeln!(config, r#"{{"setting": true}}"#)?;
    
    // List files in the directory
    for entry in std::fs::read_dir(dir.path())? {
        let entry = entry?;
        println!("Found: {:?}", entry.path());
    }
    
    // Directory and all contents deleted when `dir` goes out of scope
    
    Ok(())
}

tempdir() creates a named directory you can use to organize multiple temporary files.

Cleanup Guarantees

use tempfile::{tempfile, tempdir};
use std::fs;
 
fn main() -> std::io::Result<()> {
    // tempfile cleanup: Immediate and reliable
    // On Unix: file is unlinked immediately after creation
    // The file never has a directory entry
    // Space is reclaimed when handle is closed
    
    {
        let _file = tempfile()?;
        // File exists but has no directory entry
        // Even if program crashes, OS reclaims the space
    } // Handle closed, resources freed
    
    // tempdir cleanup: Depends on destructor
    // Directory and contents deleted when TempDir is dropped
    
    let dir_path;
    {
        let dir = tempdir()?;
        dir_path = dir.path().to_path_buf();
        
        // Create a file inside
        fs::write(dir.path().join("test.txt"), "data")?;
        
        // Path exists
        assert!(dir_path.exists());
        
        // If we forget to drop, cleanup won't happen
        // std::mem::forget(dir);  // Uncommenting leaks the directory
    } // dir dropped, directory deleted
    
    // Directory no longer exists
    assert!(!dir_path.exists());
    
    Ok(())
}

tempfile has stronger cleanup guarantees because the OS handles it; tempdir relies on Rust's destructor.

Anonymous Files vs Named Paths

use tempfile::{tempfile, NamedTempFile, tempdir};
use std::path::PathBuf;
 
fn main() -> std::io::Result<()> {
    // tempfile() - anonymous, no path
    let anon_file = tempfile()?;
    // Cannot get a path - the file doesn't have one
    // (or it's already unlinked on Unix)
    
    // Useful for:
    // - Scratch space for computation
    // - Memory-mapped temporary storage
    // - Intermediate data processing
    
    // tempdir() - named directory, files have paths
    let dir = tempdir()?;
    let file_path = dir.path().join("output.txt");
    
    // Path can be shared with:
    // - Child processes
    // - External commands
    // - C libraries that need file paths
    
    // NamedTempFile - middle ground
    // Creates a temp file WITH a path
    use tempfile::NamedTempFile;
    let named_temp = NamedTempFile::new()?;
    
    println!("Named temp file path: {:?}", named_temp.path());
    // Path is available but file is still temporary
    
    // Persist the file if needed
    let persisted_path = named_temp.into_temp_path();
    // Can also: named_temp.persist("/permanent/path.txt")?
    
    Ok(())
}

tempfile() is anonymous; tempdir() provides paths; NamedTempFile offers a middle ground.

Sharing with External Processes

use tempfile::tempdir;
use std::process::Command;
 
fn main() -> std::io::Result<()> {
    // tempdir is ideal when external processes need file paths
    let dir = tempdir()?;
    
    // Create a script file
    let script_path = dir.path().join("script.sh");
    std::fs::write(&script_path, "#!/bin/sh\necho 'Hello from script'")?;
    
    // Run external command with temp files
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        std::fs::set_permissions(&script_path, 
            std::fs::Permissions::from_mode(0o755))?;
        
        let output = Command::new(&script_path)
            .current_dir(dir.path())
            .output()?;
        
        println!("Script output: {}", String::from_utf8_lossy(&output.stdout));
    }
    
    // tempfile() wouldn't work here - no path to pass
    
    Ok(())
}

tempdir() enables scenarios where external tools need filesystem paths.

Working with Multiple Temporary Files

use tempfile::tempdir;
use std::fs::File;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    // tempdir excels at organizing multiple related temp files
    let dir = tempdir()?;
    
    // Create organized structure
    let data_dir = dir.path().join("data");
    let cache_dir = dir.path().join("cache");
    
    std::fs::create_dir(&data_dir)?;
    std::fs::create_dir(&cache_dir)?;
    
    // Create multiple data files
    for i in 0..3 {
        let path = data_dir.join(format!("file_{}.txt", i));
        let mut file = File::create(path)?;
        writeln!(file, "Data file {}", i)?;
    }
    
    // Create cache files
    let mut cache_file = File::create(cache_dir.join("cache.bin"))?;
    cache_file.write_all(b"cached data")?;
    
    // Everything is cleaned up together
    println!("Created temporary structure at: {:?}", dir.path());
    
    // Recursive cleanup handles all nested files and directories
    Ok(())
}

tempdir() provides a scope for multiple temporary resources that are cleaned up together.

Persistence and Cleanup Control

use tempfile::tempdir;
use std::path::PathBuf;
 
fn main() -> std::io::Result<()> {
    // tempdir allows keeping the directory if needed
    let dir = tempdir()?;
    
    // Do some work...
    std::fs::write(dir.path().join("output.txt"), "results")?;
    
    // Option 1: Let it be cleaned up (default)
    // dir will be deleted when it goes out of scope
    
    // Option 2: Keep the directory
    let kept_path: PathBuf = dir.into_path();
    // Now the directory won't be deleted automatically
    println!("Directory kept at: {:?}", kept_path);
    
    // You're now responsible for cleanup
    // std::fs::remove_dir_all(&kept_path)?;
    
    // For tempfile, there's no way to "keep" it
    // (Use NamedTempFile if you need this for files)
    
    Ok(())
}

tempdir() can be converted to a regular path, transferring cleanup responsibility.

Error Handling and Cleanup

use tempfile::tempdir;
use std::fs;
use std::io;
 
fn process_files() -> io::Result<()> {
    let dir = tempdir()?;
    
    // Create intermediate files
    let input = dir.path().join("input.txt");
    fs::write(&input, "input data")?;
    
    // Process might fail
    let content = fs::read_to_string(&input)?;
    if content.is_empty() {
        // Early return - dir is still cleaned up
        return Err(io::Error::new(
            io::ErrorKind::InvalidData, 
            "Empty input"
        ));
    }
    
    // More processing...
    let output = dir.path().join("output.txt");
    fs::write(&output, "processed data")?;
    
    // Success - dir cleaned up on return
    Ok(())
}
 
fn main() {
    match process_files() {
        Ok(()) => println!("Success"),
        Err(e) => println!("Error: {}", e),
    }
    
    // In both success and error cases,
    // the temporary directory is cleaned up
}

RAII ensures cleanup happens even when errors cause early returns.

Custom Locations and Prefixes

use tempfile::{tempfile_in, tempdir_in, tempfile, tempdir};
use std::path::Path;
 
fn main() -> std::io::Result<()> {
    // By default, uses system temp directory
    
    // Can specify custom location
    let custom_dir = tempdir_in("/tmp/myapp")?;
    println!("Custom temp dir: {:?}", custom_dir.path());
    
    let custom_file = tempfile_in("/tmp/myapp")?;
    // File created in specified directory
    
    // For more control, use Builder
    use tempfile::Builder;
    
    let named_temp = Builder::new()
        .prefix("myapp_")
        .suffix("_temp")
        .rand_bytes(5)
        .tempfile()?;
    
    let dir = Builder::new()
        .prefix("session_")
        .tempdir()?;
    
    println!("Session dir: {:?}", dir.path());
    
    Ok(())
}

Both tempfile and tempdir support custom locations and name prefixes.

Platform-Specific Behavior

use tempfile::tempfile;
 
fn main() -> std::io::Result<()> {
    // tempfile() behavior varies by platform:
    
    // Unix:
    // - Creates file with O_TMPFILE (Linux 3.11+) or
    // - Creates file then immediately unlinks it
    // - File never has visible directory entry
    // - Deleted automatically when closed
    // - Works even if program crashes
    
    // Windows:
    // - Creates file with FILE_FLAG_DELETE_ON_CLOSE
    // - Deleted when last handle is closed
    // - If program crashes, OS still cleans up
    
    // tempdir() behavior:
    
    // All platforms:
    // - Creates directory with unique name
    // - Deletes directory and contents in destructor
    // - If destructor doesn't run (panic, abort, etc.),
    //   directory may persist
    
    let _file = tempfile()?;
    
    // On any platform, when _file is dropped,
    // the file is cleaned up by the OS
    
    Ok(())
}

tempfile has OS-level cleanup guarantees; tempdir relies on destructors.

Memory-Mapped Temporary Files

use tempfile::tempfile;
use std::io::{Write, Seek, SeekFrom};
 
#[cfg(unix)]
fn main() -> std::io::Result<()> {
    // tempfile is ideal for memory-mapped scratch space
    use memmap2::MmapMut;
    
    let mut file = tempfile()?;
    
    // Set file size
    file.seek(SeekFrom::Start(1024 * 1024))?;  // 1 MB
    file.write_all(&[0])?;
    file.seek(SeekFrom::Start(0))?;
    
    // Memory map the file
    let mut mmap = unsafe { MmapMut::map_mut(&file)? };
    
    // Use as random-access memory
    mmap[0] = 42;
    mmap[1000] = 255;
    
    println!("First byte: {}", mmap[0]);
    
    // Changes are written to file
    // File is cleaned up when mmap and file are dropped
    
    Ok(())
}
 
#[cfg(not(unix))]
fn main() -> std::io::Result<()> {
    println!("Memory mapping example requires Unix");
    Ok(())
}

tempfile() works well for memory-mapped temporary storage that's automatically cleaned up.

Comparison Table

fn main() {
    // | Aspect              | tempfile()              | tempdir()               |
    // |---------------------|-------------------------|-------------------------|
    // | Returns             | File handle             | TempDir guard           |
    // | Has path            | No                      | Yes                     |
    // | Create files inside | No                      | Yes                     |
    // | Share with extern   | No                      | Yes                     |
    // | Cleanup guarantee   | OS-level                | Destructor-based        |
    // | Multiple resources  | No                      | Yes                     |
    // | Persist option      | No (use NamedTempFile)  | Yes (into_path)         |
    // | Use case            | Scratch data            | Organized temp files    |
    
    println!("Comparison documented above");
}

Synthesis

When to use tempfile():

Use Case Reason
Scratch space for computation No path needed, auto-cleanup
Memory-mapped temporary files OS-level cleanup is reliable
Intermediate data processing Isolated, anonymous storage
Cache that doesn't need persistence Strong cleanup guarantee

When to use tempdir():

Use Case Reason
Multiple related temp files Organize under one directory
External process integration Can pass paths to commands
Hierarchical temp structure Create subdirectories
Files needed by other code Path can be shared

Key differences:

Property tempfile() tempdir()
Anonymous Yes No
Path available No Yes
Cleanup mechanism OS-level Destructor
Crash-safe cleanup Yes Not guaranteed
Multiple resources Single file Directory tree
Persistence option NamedTempFile into_path()

Key insight: The choice between tempfile() and tempdir() is fundamentally about visibility: tempfile() creates an anonymous resource that's perfect for internal use where no path is needed, while tempdir() creates a named directory that's essential when you need to share file paths with other parts of your code or external processes. The cleanup difference matters too: tempfile() benefits from OS-level deletion guarantees that work even if your program crashes or is killed, while tempdir() cleanup depends on Rust's destructor running. For scratch data that never leaves your process, tempfile() is cleaner and safer; for anything that needs a filesystem presence, tempdir() provides the necessary structure. Both embody Rust's RAII pattern, but with different levels of guarantee—tempfile()'s guarantee is stronger because the OS handles it independently of your program's lifecycle.