How do I create temporary files and directories in Rust?

Walkthrough

The tempfile crate provides secure, cross-platform temporary file and directory handling. It automatically generates unique names, handles cleanup when the tempfile goes out of scope, and supports both named and anonymous temporary files. Temporary files are essential for testing, caching intermediate results, and processing data that shouldn't persist.

Key types:

  1. NamedTempFile — temporary file with a visible path, auto-deleted on drop
  2. TempDir — temporary directory, auto-deleted on drop
  3. tempfile() — anonymous temporary file (no path, more secure)
  4. tempdir() — temporary directory in system temp location
  5. SpooledTempFile — in-memory buffer that spills to disk when large

Tempfile handles platform differences automatically and provides secure file permissions.

Code Example

# Cargo.toml
[dependencies]
tempfile = "3"
use tempfile::NamedTempFile;
use std::io::{Write, Read};
 
fn main() -> std::io::Result<()> {
    // Create a named temporary file
    let mut temp_file = NamedTempFile::new()?;
    
    // Write to it
    writeln!(temp_file, "Hello, temporary file!")?;
    
    // Get the path
    println!("Temp file path: {:?}", temp_file.path());
    
    // Read it back
    temp_file.seek(std::io::SeekFrom::Start(0))?;
    let mut contents = String::new();
    temp_file.read_to_string(&mut contents)?;
    println!("Contents: {}", contents);
    
    // File is automatically deleted when temp_file goes out of scope
    Ok(())
}

Basic Temporary Files

use tempfile::{NamedTempFile, tempfile};
use std::io::{Write, Read, Seek, SeekFrom};
 
fn main() -> std::io::Result<()> {
    // Anonymous temporary file (no path on filesystem)
    let mut anon = tempfile()?;
    writeln!(anon, "Anonymous temp file")?;
    println!("Anonymous temp file has no path");
    // File is deleted immediately after creation on Unix
    // It exists only as an open file handle
    
    // Named temporary file (has a path)
    let mut named = NamedTempFile::new()?;
    writeln!(named, "Named temp file")?;
    println!("Named temp file path: {:?}", named.path());
    // File is deleted when `named` is dropped
    
    // Named temp file with custom prefix/suffix
    let mut custom = NamedTempFile::with_prefix("myapp_")?;
    writeln!(custom, "Custom named temp")?;
    println!("Custom temp: {:?}", custom.path());
    
    let mut custom2 = NamedTempFile::with_suffix(".dat")?;
    writeln!(custom2, "With suffix")?;
    println!("Suffixed temp: {:?}", custom2.path());
    
    let mut custom3 = NamedTempFile::with_prefix_and_suffix("myapp_", ".tmp")?;
    writeln!(custom3, "Both prefix and suffix")?;
    println!("Full custom: {:?}", custom3.path());
    
    // Named temp file in a specific directory
    let mut in_dir = NamedTempFile::new_in("./")?;
    writeln!(in_dir, "Temp file in current directory")?;
    println!("In current dir: {:?}", in_dir.path());
    
    Ok(())
}

Temporary Directories

use tempfile::{tempdir, TempDir};
use std::io::Write;
use std::fs::{self, File};
 
fn main() -> std::io::Result<()> {
    // Create a temporary directory
    let temp_dir = tempdir()?;
    println!("Temp directory: {:?}", temp_dir.path());
    
    // Create files inside
    let file_path = temp_dir.path().join("data.txt");
    let mut file = File::create(&file_path)?;
    writeln!(file, "Data in temp directory")?;
    
    // Create nested structure
    let nested_dir = temp_dir.path().join("nested").join("deep");
    fs::create_dir_all(&nested_dir)?;
    
    let nested_file = nested_dir.join("nested_data.txt");
    let mut file = File::create(&nested_file)?;
    writeln!(file, "Nested data")?;
    
    // List contents
    println!("Contents of temp dir:");
    for entry in fs::read_dir(temp_dir.path())? {
        let entry = entry?;
        println!("  {:?}", entry.path());
    }
    
    // Directory is deleted when temp_dir goes out of scope
    println!("Directory exists: {}", temp_dir.path().exists());
    
    Ok(())
    // temp_dir is dropped here, directory is deleted
}
 
// TempDir with custom settings
fn custom_temp_dir() -> std::io::Result<()> {
    // With prefix
    let dir = tempfile::Builder::new()
        .prefix("myapp_")
        .tempdir()?;
    println!("Prefixed dir: {:?}", dir.path());
    
    // In specific location
    let dir = tempfile::Builder::new()
        .prefix("myapp_")
        .tempdir_in("./")?;
    println!("In current dir: {:?}", dir.path());
    
    // With random suffix length
    let dir = tempfile::Builder::new()
        .prefix("myapp_")
        .rand_bytes(12)  // Longer random suffix
        .tempdir()?;
    println!("Longer suffix: {:?}", dir.path());
    
    Ok(())
}

Persisting Temporary Files

use tempfile::NamedTempFile;
use std::io::{Write, Read};
use std::path::PathBuf;
 
fn main() -> std::io::Result<()> {
    // Create a temp file
    let mut temp = NamedTempFile::new()?;
    writeln!(temp, "Important data")?;
    
    println!("Temp file: {:?}", temp.path());
    
    // Persist the file to a permanent location
    let persisted_path = PathBuf::from("./persisted_data.txt");
    let mut persisted = temp.persist(&persisted_path)?;
    
    println!("Persisted to: {:?}", persisted_path);
    println!("Temp file no longer exists at original path");
    
    // Can still use the file handle
    persisted.seek(std::io::SeekFrom::Start(0))?;
    let mut contents = String::new();
    persisted.read_to_string(&mut contents)?;
    println!("Contents: {}", contents);
    
    // Clean up persisted file
    std::fs::remove_file(&persisted_path)?;
    
    // Alternative: keep the temp file and get its path
    let temp2 = NamedTempFile::new()?;
    let (file, path) = temp2.into_parts();
    println!("File handle and path separated: {:?}", path);
    // Now you're responsible for cleaning up the file
    std::fs::remove_file(&path)?;
    
    Ok(())
}
 
// Persist with error handling
fn persist_with_fallback() -> std::io::Result<()> {
    let temp = NamedTempFile::new()?;
    
    let target_path = PathBuf::from("./target_file.txt");
    
    // Try to persist, handle error
    match temp.persist(&target_path) {
        Ok(_file) => {
            println!("Successfully persisted to {:?}", target_path);
        }
        Err(e) => {
            // Error contains both the error and the original temp file
            println!("Failed to persist: {}", e.error);
            println!("Temp file still at: {:?}", e.file.path());
            // You can retry or handle the temp file
        }
    }
    
    Ok(())
}

Spooled Temporary Files

use tempfile::SpooledTempFile;
use std::io::{Write, Read, Seek, SeekFrom};
 
fn main() -> std::io::Result<()> {
    // Create a spooled temp file (in-memory until max size)
    let mut spool = SpooledTempFile::new(100);  // 100 bytes max in memory
    
    // Write small amount (stays in memory)
    write!(spool, "Small data")?;
    
    if spool.is_in_memory() {
        println!("Data is still in memory");
    }
    
    // Write more data (exceeds limit, spills to disk)
    let large_data = "x".repeat(200);
    write!(spool, "{}", large_data)?;
    
    if !spool.is_in_memory() {
        println!("Data has spilled to disk");
    }
    
    // Read back
    spool.seek(SeekFrom::Start(0))?;
    let mut contents = String::new();
    spool.read_to_string(&mut contents)?;
    println!("Read {} bytes", contents.len());
    
    // Spooled files are also cleaned up on drop
    Ok(())
}
 
// Spooled file with custom max size
fn spooled_example() -> std::io::Result<()> {
    // 1MB in-memory limit
    let mut spool = SpooledTempFile::new(1024 * 1024);
    
    // Write 500KB (stays in memory)
    let data_500kb = vec![0u8; 500 * 1024];
    spool.write_all(&data_500kb)?;
    println!("After 500KB: in_memory = {}", spool.is_in_memory());
    
    // Write another 500KB (total 1MB, still in memory)
    spool.write_all(&data_500kb)?;
    println!("After 1MB: in_memory = {}", spool.is_in_memory());
    
    // Write more (spills to disk)
    let extra = vec![0u8; 1024];
    spool.write_all(&extra)?;
    println!("After overflow: in_memory = {}", spool.is_in_memory());
    
    // Roll back to memory (only if under limit)
    let mut spool2 = SpooledTempFile::new(100);
    write!(spool2, "Small")?;
    let rolled_back = spool2.rollback_to_memory();
    println!("Rolled back: {}", rolled_back);
    
    Ok(())
}

Working with Temporary Files in Tests

use tempfile::{NamedTempFile, TempDir};
use std::io::{Write, Read};
use std::fs;
 
// Test helper to create temp file with content
fn create_temp_file(content: &str) -> std::io::Result<NamedTempFile> {
    let mut file = NamedTempFile::new()?;
    write!(file, "{}", content)?;
    Ok(file)
}
 
// Test that needs a file
#[test]
fn test_read_file() {
    let temp = create_temp_file("test content").unwrap();
    let path = temp.path();
    
    let content = fs::read_to_string(path).unwrap();
    assert_eq!(content, "test content");
    
    // temp is cleaned up at end of test
}
 
// Test that needs a directory
#[test]
fn test_directory_operations() {
    let dir = TempDir::new().unwrap();
    
    // Create test files
    fs::write(dir.path().join("a.txt"), "content a").unwrap();
    fs::write(dir.path().join("b.txt"), "content b").unwrap();
    
    // Run test
    let entries: Vec<_> = fs::read_dir(dir.path())
        .unwrap()
        .map(|e| e.unwrap().path())
        .collect();
    
    assert_eq!(entries.len(), 2);
}
 
// Test with multiple temp files
#[test]
fn test_multiple_temp_files() {
    let dir = TempDir::new().unwrap();
    
    let file1 = NamedTempFile::new_in(dir.path()).unwrap();
    let file2 = NamedTempFile::new_in(dir.path()).unwrap();
    
    // Files are cleaned up automatically
    assert!(file1.path().exists());
    assert!(file2.path().exists());
}
 
// Test with fixture
struct TestFixture {
    temp_dir: TempDir,
    config_path: std::path::PathBuf,
    data_path: std::path::PathBuf,
}
 
impl TestFixture {
    fn new() -> std::io::Result<Self> {
        let temp_dir = TempDir::new()?;
        
        let config_path = temp_dir.path().join("config.json");
        let data_path = temp_dir.path().join("data.bin");
        
        // Create initial files
        fs::write(&config_path, r#"{"setting": "value"}"#)?;
        fs::write(&data_path, vec![0u8; 100])?;
        
        Ok(Self {
            temp_dir,
            config_path,
            data_path,
        })
    }
    
    fn config_path(&self) -> &std::path::Path {
        &self.config_path
    }
    
    fn data_path(&self) -> &std::path::Path {
        &self.data_path
    }
}
 
#[test]
fn test_with_fixture() {
    let fixture = TestFixture::new().unwrap();
    
    // Use fixture paths
    assert!(fixture.config_path().exists());
    assert!(fixture.data_path().exists());
    
    // temp_dir cleaned up when fixture is dropped
}

Automatic Cleanup vs Manual Control

use tempfile::{NamedTempFile, TempDir, tempdir};
use std::io::Write;
use std::fs;
 
fn main() -> std::io::Result<()> {
    // Automatic cleanup (default)
    {
        let temp = NamedTempFile::new()?;
        writeln!(temp, "Auto-cleaned")?;
        println!("Temp file: {:?}", temp.path());
        // temp is dropped here, file is deleted
    }
    
    // Manual cleanup control
    let temp = NamedTempFile::new()?;
    writeln!(temp, "Manual control")?;
    
    // Close without deleting
    let (file, path) = temp.into_parts();
    drop(file);  // Close file handle, but file remains
    
    println!("File still exists: {}", path.exists());
    
    // Manual cleanup
    fs::remove_file(&path)?;
    println!("File manually deleted");
    
    // Temporary directory cleanup
    {
        let dir = tempdir()?;
        fs::write(dir.path().join("test.txt"), "content")?;
        println!("Dir: {:?}", dir.path());
        // dir is dropped here, directory and contents deleted
    }
    
    // Keep directory by converting to path
    let dir = tempdir()?;
    let kept_path = dir.into_path();
    
    fs::write(kept_path.join("persistent.txt"), "stays")?;
    println!("Kept path: {:?}", kept_path);
    // Directory is NOT deleted
    
    // Clean up manually
    fs::remove_dir_all(&kept_path)?;
    
    Ok(())
}
 
// Cleanup on close behavior
fn cleanup_behavior() -> std::io::Result<()> {
    // Create temp file with close behavior
    let mut temp = NamedTempFile::new()?;
    writeln!(temp, "Testing cleanup")?;
    
    // Default: file deleted on drop
    println!("Path: {:?}", temp.path());
    
    // Persist to keep it
    let persisted = temp.persist("./kept.txt")?;
    println!("Persisted to ./kept.txt");
    
    // Clean up
    fs::remove_file("./kept.txt")?;
    
    Ok(())
}

Creating Temp Files in Specific Locations

use tempfile::{NamedTempFile, TempDir};
use std::io::Write;
use std::path::Path;
 
fn main() -> std::io::Result<()> {
    // In current directory
    let temp_here = NamedTempFile::new_in(".")?;
    println!("In current dir: {:?}", temp_here.path());
    
    // In specific directory
    let custom_dir = Path::new("./temp_files");
    fs_extra::create_dir_all(custom_dir)?;
    
    let temp_custom = NamedTempFile::new_in(custom_dir)?;
    println!("In custom dir: {:?}", temp_custom.path());
    
    // Temp directory in specific location
    let dir_in_custom = TempDir::new_in(custom_dir)?;
    println!("Dir in custom: {:?}", dir_in_custom.path());
    
    // Clean up
    std::fs::remove_dir_all(custom_dir)?;
    
    Ok(())
}
 
mod fs_extra {
    use std::fs;
    use std::path::Path;
    use std::io;
    
    pub fn create_dir_all(path: &Path) -> io::Result<()> {
        fs::create_dir_all(path)
    }
}
 
// System temp directory
fn system_temp() -> std::io::Result<()> {
    // Get system temp directory
    let temp_root = std::env::temp_dir();
    println!("System temp dir: {:?}", temp_root);
    
    // Create temp file there (default behavior)
    let temp = NamedTempFile::new()?;
    assert!(temp.path().starts_with(&temp_root));
    
    // Temp dir in system temp (default)
    let dir = TempDir::new()?;
    assert!(dir.path().starts_with(&temp_root));
    
    Ok(())
}

File Permissions and Security

use tempfile::NamedTempFile;
use std::io::Write;
 
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
 
fn main() -> std::io::Result<()> {
    // tempfile creates files with secure permissions (0600 on Unix)
    let mut temp = NamedTempFile::new()?;
    writeln!(temp, "Sensitive data")?;
    
    println!("Temp file: {:?}", temp.path());
    
    // On Unix, check permissions
    #[cfg(unix)]
    {
        let metadata = std::fs::metadata(temp.path())?;
        let mode = metadata.permissions().mode();
        println!("Permissions: {:o}", mode & 0o777);
        // Should be 600 (rw-------)
    }
    
    // tempfile() creates anonymous files
    // These are immediately unlinked on Unix, so they don't
    // appear in directory listings and are automatically cleaned
    // up by the OS when the file handle is closed
    
    let mut anon = tempfile::tempfile()?;
    writeln!(anon, "Anonymous file");
    // No path, more secure for sensitive data
    
    Ok(())
}
 
// Secure temp file for sensitive data
fn secure_temp() -> std::io::Result<()> {
    // Use anonymous tempfile for sensitive data
    // No path on filesystem, can't be accessed by other processes
    let mut secure = tempfile::tempfile()?;
    
    writeln!(secure, "Password or secret")?;
    
    // File never had a name, never appeared in filesystem
    // Automatically cleaned up by OS
    
    Ok(())
}

Real-World Use Cases

use tempfile::{NamedTempFile, TempDir, tempdir};
use std::io::{Write, Read, BufWriter, BufReader};
use std::fs::{self, File};
use std::process::Command;
 
// Atomic file write pattern
fn atomic_write(path: &std::path::Path, content: &str) -> std::io::Result<()> {
    // Write to temp file first
    let mut temp = NamedTempFile::new_in(path.parent().unwrap_or(std::path::Path::new(".")))?;
    write!(temp, "{}", content)?;
    
    // Sync to disk
    temp.flush()?;
    
    // Atomically rename to target
    temp.persist(path)?;
    
    Ok(())
}
 
// Process file with external command
fn process_with_command(input: &str) -> std::io::Result<String> {
    // Create temp file for input
    let mut input_file = NamedTempFile::new()?;
    write!(input_file, "{}", input)?;
    
    // Create temp file for output
    let output_file = NamedTempFile::new()?;
    let output_path = output_file.path().to_owned();
    
    // Run external command (example: cat)
    let status = Command::new("cat")
        .arg(input_file.path())
        .output()?;
    
    // Read result
    let result = String::from_utf8_lossy(&status.stdout).to_string();
    
    Ok(result)
}
 
// Multi-step processing with temp directory
fn multi_step_processing(data: &[u8]) -> std::io::Result<Vec<u8>> {
    let temp_dir = tempdir()?;
    
    // Step 1: Write input
    let input_path = temp_dir.path().join("input.bin");
    fs::write(&input_path, data)?;
    
    // Step 2: Process (simulate)
    let intermediate_path = temp_dir.path().join("intermediate.bin");
    let intermediate: Vec<u8> = data.iter().map(|b| b.wrapping_add(1)).collect();
    fs::write(&intermediate_path, &intermediate)?;
    
    // Step 3: Final output
    let output_path = temp_dir.path().join("output.bin");
    let output: Vec<u8> = intermediate.iter().map(|b| b.wrapping_add(1)).collect();
    fs::write(&output_path, &output)?;
    
    // Read result
    let result = fs::read(&output_path)?;
    
    // All temp files cleaned up automatically
    Ok(result)
}
 
// Buffered writing to temp file
fn buffered_temp_write(lines: &[&str]) -> std::io::Result<String> {
    let temp = NamedTempFile::new()?;
    
    // Use buffered writer for efficiency
    let mut writer = BufWriter::new(&temp);
    for line in lines {
        writeln!(writer, "{}", line)?;
    }
    writer.flush()?;
    
    // Read back
    let mut reader = BufReader::new(&temp);
    let mut contents = String::new();
    reader.read_to_string(&mut contents)?;
    
    Ok(contents)
}
 
// Temporary workspace for file operations
struct Workspace {
    dir: TempDir,
}
 
impl Workspace {
    fn new() -> std::io::Result<Self> {
        Ok(Self {
            dir: tempdir()?,
        })
    }
    
    fn create_file(&self, name: &str, content: &str) -> std::io::Result<std::path::PathBuf> {
        let path = self.dir.path().join(name);
        fs::write(&path, content)?;
        Ok(path)
    }
    
    fn path(&self) -> &std::path::Path {
        self.dir.path()
    }
}
 
fn main() -> std::io::Result<()> {
    // Atomic write
    atomic_write(std::path::Path::new("./test_atomic.txt"), "Hello, atomic!")?;
    println!("Atomic write complete");
    fs::remove_file("./test_atomic.txt")?;
    
    // Process with command
    let result = process_with_command("test input")?;
    println!("Processed: {}", result);
    
    // Multi-step processing
    let data = vec![1, 2, 3, 4, 5];
    let processed = multi_step_processing(&data)?;
    println!("Processed data: {:?}", processed);
    
    // Buffered temp write
    let lines = vec!["line 1", "line 2", "line 3"];
    let content = buffered_temp_write(&lines)?;
    println!("Buffered content:\n{}", content);
    
    // Workspace
    let ws = Workspace::new()?;
    ws.create_file("config.txt", "setting=value")?;
    ws.create_file("data.txt", "important data")?;
    println!("Workspace: {:?}", ws.path());
    
    Ok(())
}

Error Handling

use tempfile::{NamedTempFile, PersistError};
use std::io;
use std::path::PathBuf;
 
fn handle_tempfile_errors() -> Result<(), Box<dyn std::error::Error>> {
    // Create temp file
    let temp = NamedTempFile::new()?;  // io::Error
    
    // Persist with error handling
    let target = PathBuf::from("/root/protected.txt");  // May fail due to permissions
    
    match temp.persist(&target) {
        Ok(_file) => {
            println!("Successfully persisted");
        }
        Err(PersistError { error, file }) => {
            // error: the io::Error that occurred
            // file: the NamedTempFile you can retry with
            eprintln!("Failed to persist: {}", error);
            println!("Temp file still at: {:?}", file.path());
            
            // Retry with different location
            let fallback = PathBuf::from("./fallback.txt");
            file.persist(&fallback)?;
        }
    }
    
    Ok(())
}
 
// Custom error type
#[derive(Debug)]
enum TempFileError {
    Io(io::Error),
    Persist(String),
}
 
impl From<io::Error> for TempFileError {
    fn from(err: io::Error) -> Self {
        TempFileError::Io(err)
    }
}
 
impl From<PersistError> for TempFileError {
    fn from(err: PersistError) -> Self {
        TempFileError::Persist(format!("{}: {:?}", err.error, err.file.path()))
    }
}
 
fn safe_temp_operation() -> Result<NamedTempFile, TempFileError> {
    let mut temp = NamedTempFile::new()?;
    writeln!(temp, "data")?;
    Ok(temp)
}

Summary

  • Use tempfile() for anonymous temporary files (no filesystem path)
  • Use NamedTempFile::new() for temporary files with a path
  • Use tempdir() for temporary directories
  • Use NamedTempFile::with_prefix("myapp_") for custom filename prefix
  • Use NamedTempFile::new_in(dir) to create in specific directory
  • Files and directories are automatically cleaned up when dropped
  • Use temp.persist(path) to keep a temp file permanently
  • Use temp.into_parts() to get file handle and path separately
  • Use SpooledTempFile::new(max_bytes) for in-memory files that spill to disk
  • Check spool.is_in_memory() to see if data is still in RAM
  • Use tempfile::Builder for advanced configuration
  • On Unix, temp files are created with secure permissions (0600)
  • Anonymous tempfiles (tempfile()) are more secure as they have no path
  • Use temp files in tests for automatic cleanup
  • Pattern: write to temp file, then persist() for atomic writes
  • PersistError contains both the error and the original temp file for retry