How do I work with Tempfile for Temporary File Handling in Rust?

Walkthrough

Tempfile is a crate for creating and managing temporary files and directories in Rust. It provides secure, automatic cleanup of temporary resources, handling edge cases like race conditions and ensuring files are properly deleted when no longer needed.

Key concepts:

  • NamedTempFile — A temporary file with a visible path in the filesystem
  • TempDir — A temporary directory that deletes itself on drop
  • SpooledTempFile — In-memory buffer that spills to disk when large
  • tempfile() — Anonymous temporary file (no path, most secure)
  • tempdir() — Create temporary directory with automatic cleanup

When to use Tempfile:

  • Unit tests needing file I/O
  • Intermediate processing files
  • Download/upload buffering
  • Temporary configuration files
  • Cache files with automatic cleanup

When NOT to use Tempfile:

  • Persistent user data
  • Files that need to survive restarts
  • When you need explicit user control over file location

Code Examples

Basic Temporary File

use tempfile::NamedTempFile;
use std::io::{Write, Read};
 
fn main() -> std::io::Result<()> {
    // Create a named temporary file
    let mut temp_file = NamedTempFile::new()?;
    
    // Get the path
    println!("Temp file: {:?}", temp_file.path());
    
    // Write to it
    writeln!(temp_file, "Hello, temp file!")?;
    
    // 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(())
}

Temporary Directory

use tempfile::TempDir;
use std::fs::File;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    // Create a temporary directory
    let temp_dir = TempDir::new()?;
    println!("Temp dir: {:?}", 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 dir")?;
    
    // Directory and contents deleted on drop
    Ok(())
}

Anonymous Temporary File

use tempfile::tempfile;
use std::io::{Write, Read, Seek, SeekFrom};
 
fn main() -> std::io::Result<()> {
    // Create an anonymous temp file (no path, more secure)
    let mut file = tempfile()?;
    
    // Write data
    file.write_all(b"Anonymous temp data")?;
    
    // Read back
    file.seek(SeekFrom::Start(0))?;
    let mut buf = Vec::new();
    file.read_to_end(&mut buf)?;
    println!("Read: {}", String::from_utf8_lossy(&buf));
    
    // File deleted on close (or drop)
    Ok(())
}

Spooled Temporary File

use tempfile::SpooledTempFile;
use std::io::{Write, Read, Seek, SeekFrom};
 
fn main() -> std::io::Result<()> {
    // In-memory buffer up to 1KB, then spills to disk
    let mut spool = SpooledTempFile::new(1024);
    
    // Small data stays in memory
    write!(spool, "Small data")?;
    
    // Check if still in memory
    if spool.is_rolled_over() {
        println!("Data spilled to disk");
    } else {
        println!("Data still in memory");
    }
    
    // Large data spills to disk
    let large_data = vec![0u8; 2048];
    spool.write_all(&large_data)?;
    
    if spool.is_rolled_over() {
        println!("Now spilled to disk");
    }
    
    Ok(())
}

Persist Temporary File

use tempfile::NamedTempFile;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    writeln!(temp, "Important data")?;
    
    // Persist the file at a specific location
    let persisted = temp.persist("/tmp/important.txt")?;
    
    println!("File persisted to: {:?}", persisted.path());
    
    // File is no longer temporary - won't be deleted on drop
    Ok(())
}

Custom Location

use tempfile::NamedTempFile;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    // Create temp file in specific directory
    let mut temp = NamedTempFile::new_in("./output")?;
    writeln!(temp, "In custom location")?;
    
    println!("Created in: {:?}", temp.path().parent().unwrap());
    
    Ok(())
}

With Prefix and Suffix

use tempfile::Builder;
 
fn main() -> std::io::Result<()> {
    // Named temp file with prefix/suffix
    let temp = Builder::new()
        .prefix("myapp_")
        .suffix(".tmp")
        .tempfile()?;
    
    println!("Temp file: {:?}", temp.path());
    
    // Temp dir with prefix
    let dir = Builder::new()
        .prefix("myapp_session_")
        .tempdir()?;
    
    println!("Temp dir: {:?}", dir.path());
    
    Ok(())
}

Testing with Temp Files

use tempfile::NamedTempFile;
use std::io::{Write, Read};
 
fn process_file(path: &std::path::Path) -> std::io::Result<String> {
    let mut file = std::fs::File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents.to_uppercase())
}
 
#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::NamedTempFile;
    use std::io::Write;
    
    #[test]
    fn test_process_file() {
        let mut temp = NamedTempFile::new().unwrap();
        write!(temp, "hello world").unwrap();
        
        let result = process_file(temp.path()).unwrap();
        assert_eq!(result, "HELLO WORLD");
    }
}
 
fn main() {
    println!("Run tests with: cargo test");
}

Append Mode

use tempfile::NamedTempFile;
use std::io::{Write, Seek, SeekFrom};
 
fn main() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    
    // Write initial data
    writeln!(temp, "Line 1")?;
    
    // Seek to end for append
    temp.seek(SeekFrom::End(0))?;
    writeln!(temp, "Line 2")?;
    
    // Read all
    temp.seek(SeekFrom::Start(0))?;
    let mut contents = String::new();
    std::io::Read::read_to_string(&mut temp, &mut contents)?;
    println!("{}", contents);
    
    Ok(())
}

Temporary File as Pipe

use tempfile::NamedTempFile;
use std::io::{Write, Read};
 
fn producer(temp: &mut NamedTempFile) -> std::io::Result<()> {
    writeln!(temp, "Produced data")?;
    temp.flush()?;
    Ok(())
}
 
fn consumer(temp: &mut NamedTempFile) -> std::io::Result<String> {
    use std::io::Seek;
    temp.seek(std::io::SeekFrom::Start(0))?;
    let mut data = String::new();
    temp.read_to_string(&mut data)?;
    Ok(data)
}
 
fn main() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    
    producer(&mut temp)?;
    let result = consumer(&mut temp)?;
    
    println!("Consumed: {}", result);
    Ok(())
}

Into File

use tempfile::NamedTempFile;
use std::fs::File;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    writeln!(temp, "Data")?;
    
    // Convert to regular File (loses automatic cleanup!)
    let file: File = temp.into_file();
    // Now you manage the file manually
    
    // Or keep the NamedTempFile for auto-cleanup
    // let file: &File = temp.as_file();
    
    Ok(())
}

Multiple Files in Temp Dir

use tempfile::TempDir;
use std::fs::File;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    let dir = TempDir::new()?;
    
    // Create multiple files
    for i in 0..5 {
        let path = dir.path().join(format!("file{}.txt", i));
        let mut file = File::create(path)?;
        writeln!(file, "Content {}", i)?;
    }
    
    // List files
    for entry in std::fs::read_dir(dir.path())? {
        let entry = entry?;
        println!("File: {:?}", entry.file_name());
    }
    
    // All deleted with dir on drop
    Ok(())
}

Cleanup on Error

use tempfile::NamedTempFile;
use std::io::Write;
 
fn write_config() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    
    // Write data
    write!(temp, "config data")?;
    
    // Simulate error - temp file is cleaned up
    // return Err(std::io::Error::new(std::io::ErrorKind::Other, "oops"));
    
    // Or persist on success
    temp.persist("config.txt")?;
    Ok(())
}
 
fn main() {
    match write_config() {
        Ok(()) => println!("Config saved"),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Large File Handling

use tempfile::NamedTempFile;
use std::io::Write;
 
fn main() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    
    // Write large data in chunks
    for chunk in 0..100 {
        let data = vec![chunk as u8; 1024];  // 1KB chunks
        temp.write_all(&data)?;
    }
    
    println!("Wrote {} bytes", temp.as_file().metadata()?.len());
    
    Ok(())
}

Tempfile for Downloads

use tempfile::NamedTempFile;
use std::io::{Write, Copy};
 
// Simulated download function
fn download_to_temp(url: &str) -> std::io::Result<NamedTempFile> {
    let mut temp = NamedTempFile::new()?;
    
    // Simulate downloading
    writeln!(temp, "Downloaded from: {}", url)?;
    
    Ok(temp)
}
 
fn main() -> std::io::Result<()> {
    let temp = download_to_temp("https://example.com/file")?;
    
    // Process downloaded data
    println!("Downloaded to: {:?}", temp.path());
    
    // Or persist to final location
    temp.persist("downloaded_file.txt")?;
    
    Ok(())
}

Atomic File Writes

use tempfile::NamedTempFile;
use std::io::Write;
use std::path::Path;
 
fn atomic_write(path: &Path, content: &str) -> std::io::Result<()> {
    // Create temp file in same directory for atomic rename
    let mut temp = NamedTempFile::new_in(path.parent().unwrap())?;
    
    // Write content
    write!(temp, "{}", content)?;
    temp.flush()?;
    
    // Atomic rename (persist)
    temp.persist(path)?;
    
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    atomic_write(std::path::Path::new("config.txt"), "config data")?;
    println!("Wrote atomically");
    Ok(())
}

Getting File Handle

use tempfile::NamedTempFile;
use std::fs::File;
 
fn main() -> std::io::Result<()> {
    let mut temp = NamedTempFile::new()?;
    
    // Get reference to underlying File
    let file: &File = temp.as_file();
    println!("Metadata: {:?}", file.metadata()?);
    
    // Get mutable reference
    let file_mut: &mut File = temp.as_file_mut();
    println!("Can modify: {:?}", file_mut);
    
    Ok(())
}

Error Handling

use tempfile::NamedTempFile;
 
fn main() {
    match NamedTempFile::new() {
        Ok(temp) => {
            println!("Created: {:?}", temp.path());
        }
        Err(e) => {
            eprintln!("Failed to create temp file: {}", e);
            // Common errors:
            // - No permission
            // - Disk full
            // - Invalid path
        }
    }
}

TempDir with IntoPath

use tempfile::TempDir;
use std::path::PathBuf;
 
fn main() -> std::io::Result<()> {
    let temp_dir = TempDir::new()?;
    
    // Get path without keeping TempDir alive
    // WARNING: No automatic cleanup!
    let path: PathBuf = temp_dir.into_path();
    println!("Path: {:?}", path);
    
    // Now you must clean up manually
    // std::fs::remove_dir_all(&path)?;
    
    Ok(())
}

Comparing Temp File Types

use tempfile::{tempfile, NamedTempFile, SpooledTempFile};
 
fn main() -> std::io::Result<()> {
    // tempfile() - Anonymous, no path, most secure
    let anon = tempfile()?;
    // No .path() method - can't access by path
    
    // NamedTempFile - Has path, can be reopened
    let named = NamedTempFile::new()?;
    println!("Named path: {:?}", named.path());
    
    // SpooledTempFile - In memory up to limit
    let spooled = SpooledTempFile::new(1024);
    // No .path() - may or may not be on disk
    
    println!("Three types created");
    
    Ok(())
}

Summary

Tempfile Key Imports:

use tempfile::{NamedTempFile, TempDir, tempfile, tempdir, SpooledTempFile, Builder};

Main Types:

Type Description Auto-Cleanup
NamedTempFile Temp file with path Yes
TempDir Temporary directory Yes
tempfile() Anonymous temp file Yes
SpooledTempFile In-memory with disk spill Yes

Builder Pattern:

let temp = Builder::new()
    .prefix("app_")
    .suffix(".tmp")
    .tempfile()?;

Key Methods:

// NamedTempFile
temp.path();              // Get path
temp.as_file();           // Get &File
temp.persist(path);       // Keep file permanently
 
// TempDir
dir.path();               // Get path
dir.into_path();          // Keep dir (no cleanup)

Key Points:

  • Files/dirs auto-delete on drop
  • Use persist() to keep files
  • Use new_in() for custom location
  • tempfile() is most secure (anonymous)
  • SpooledTempFile for memory efficiency
  • Great for tests and atomic writes
  • Cleanup happens even on panic

Common Patterns:

  1. Testing file I/O:
let temp = NamedTempFile::new()?;
// use temp.path() in tests
  1. Atomic writes:
let temp = NamedTempFile::new_in(dir)?;
temp.persist(final_path)?;
  1. Large data buffering:
let mut spool = SpooledTempFile::new(max_memory);