Loading pageā¦
Rust walkthroughs
Loading pageā¦
tempfile::tempdir ensure automatic cleanup even if the program panics?tempfile::tempdir creates a temporary directory and returns a TempDir guard that implements Drop to remove the directory and its contents when the guard goes out of scope, and because Rust's panic unwinding guarantees that destructors run for stack-allocated values in the unwinding path, the cleanup happens even during panics. The TempDir type is a RAII (Resource Acquisition Is Initialization) guard that holds the directory path and cleans up on drop. The cleanup is robust against panics because unwinding executes destructors, but it only works if the panic is caught and the program doesn't abortāthe directory leaks if the process is killed or std::process::exit is called.
use tempfile::tempdir;
use std::fs::File;
use std::io::Write;
fn basic_usage() -> std::io::Result<()> {
// Create a temporary directory
let temp_dir = tempdir()?;
// The directory exists and can be used
let file_path = temp_dir.path().join("example.txt");
let mut file = File::create(&file_path)?;
file.write_all(b"Hello, temp!")?;
// temp_dir goes out of scope here
// Directory and its contents are automatically removed
Ok(())
}tempdir() returns a TempDir that cleans up when dropped.
use tempfile::TempDir;
use std::path::PathBuf;
fn guard_type() {
// TempDir is a RAII guard
// It implements Drop to clean up the directory
let temp_dir: TempDir = tempfile::tempdir().unwrap();
// TempDir holds:
// - The path to the temporary directory
// - A flag indicating whether cleanup happened
let path: &std::path::Path = temp_dir.path();
println!("Temp dir at: {}", path.display());
// When temp_dir is dropped:
// 1. Drop::drop is called
// 2. std::fs::remove_dir_all is called on the path
// 3. The directory and all contents are removed
}TempDir is a RAII guard that removes the directory in its Drop implementation.
use tempfile::TempDir;
use std::path::PathBuf;
fn drop_cleanup() {
// Conceptually, TempDir's Drop looks like:
//
// impl Drop for TempDir {
// fn drop(&mut self) {
// let _ = std::fs::remove_dir_all(self.path());
// }
// }
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().to_pathBuf();
// Drop happens automatically when temp_dir goes out of scope
// This is guaranteed by Rust's ownership system
// The cleanup happens even if the scope exits via:
// - Normal return
// - Early return (return, ?)
// - Panic (with unwinding)
}The Drop trait implementation guarantees cleanup when the guard goes out of scope.
use tempfile::tempdir;
fn panic_cleanup() {
let result = std::panic::catch_unwind(|| {
// Create temp directory
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// Verify directory exists
assert!(path.exists());
// Panic while temp_dir is in scope
panic!("Intentional panic!");
// temp_dir is still dropped during unwinding
});
assert!(result.is_err()); // Panic was caught
// The temp directory was cleaned up despite the panic
// Because destructors run during unwinding
}During panic unwinding, all destructors on the stack are called, including TempDir's.
use tempfile::tempdir;
use std::fs::File;
use std::io::Write;
fn unwinding_process() {
// When a panic occurs:
// 1. The panic starts at the panic!() call
// 2. The runtime begins unwinding the stack
// 3. For each stack frame, destructors are called
// 4. Unwinding continues until panic is caught or program ends
let result = std::panic::catch_unwind(|| {
// Frame 1: outer function (catch_unwind)
// Frame 2: closure
let temp_dir = tempdir().unwrap(); // TempDir on stack
let path = temp_dir.path().to_path_buf();
// Frame 3: inner function
inner_function(&path).unwrap();
// If inner_function panics:
// 1. Unwinding starts in inner_function
// 2. Returns to Frame 2
// 3. temp_dir's destructor runs -> cleanup!
// 4. Returns to Frame 1
// 5. catch_unwind catches the panic
});
// temp_dir is cleaned up even though we panicked in inner_function
}
fn inner_function(path: &std::path::Path) -> std::io::Result<()> {
// Do some work that might panic
let mut file = File::create(path.join("data.txt"))?;
file.write_all(b"data")?;
panic!("Something went wrong!");
Ok(())
}Unwinding walks the stack and calls destructors, ensuring TempDir cleans up.
use tempfile::tempdir;
fn cleanup_limitations() {
// Scenario 1: Program abort (no unwinding)
// If panic = "abort" in Cargo.toml, destructors don't run
// Temp directory leaks
// Scenario 2: Process killed (SIGKILL, task manager)
// Process terminates immediately
// Temp directory leaks
// Scenario 3: std::process::exit()
// Destructors don't run
// Temp directory leaks
// Scenario 4: Power failure, system crash
// Temp directory obviously leaks
// These scenarios are outside Rust's control
// tempdir only guarantees cleanup during normal unwinding
}Cleanup is not guaranteed for aborts, exit(), or process termination.
use tempfile::tempdir;
// In Cargo.toml:
// [profile.dev]
// panic = "unwind" (default) - destructors run
// panic = "abort" - destructors don't run, temp leaks
fn panic_behavior() {
let result = std::panic::catch_unwind(|| {
let temp_dir = tempdir().unwrap();
// With panic = "unwind":
// panic!() triggers unwinding
// temp_dir's Drop runs
// Directory is cleaned up
// With panic = "abort":
// panic!() terminates immediately
// No unwinding, no destructors
// Directory leaks
panic!("Test panic");
});
// With unwind: result is Err, cleanup happened
// With abort: program terminates, no cleanup
}panic = "abort" bypasses destructors entirely, causing temp directory leaks.
use tempfile::tempdir;
fn exit_bypasses_drop() {
let temp_dir = tempdir().unwrap();
// Create files in temp directory...
// std::process::exit terminates immediately
// WITHOUT running destructors
// Temp directory leaks!
// Uncommenting this would leak:
// std::process::exit(0);
// Instead, return normally or propagate errors
// to let destructors run
}std::process::exit skips destructors, causing resource leaks.
use tempfile::tempdir;
use std::path::PathBuf;
fn manual_cleanup() {
// into_path() consumes the TempDir
// Returns the path WITHOUT cleanup obligation
let temp_dir = tempdir().unwrap();
let path: PathBuf = temp_dir.into_path();
// Now you're responsible for cleanup
// The directory will NOT be automatically removed
// Use this when:
// - You need the directory to persist
// - Another process needs to access it after your process ends
// - You want to control cleanup timing explicitly
// Manual cleanup:
std::fs::remove_dir_all(&path).ok();
}into_path() transfers cleanup responsibility to the caller.
use tempfile::tempdir;
fn explicit_close() {
let temp_dir = tempdir().unwrap();
// Do work with temp directory...
// Explicitly close and clean up
// close() returns Result, allowing error handling
match temp_dir.close() {
Ok(()) => println!("Cleanup successful"),
Err(e) => eprintln!("Cleanup failed: {}", e),
}
// close() is useful when you want to:
// 1. Handle cleanup errors explicitly
// 2. Clean up before the guard goes out of scope
// 3. Ensure cleanup succeeded before continuing
}close() allows explicit cleanup with error handling instead of implicit drop.
use tempfile::tempdir;
use std::fs;
fn cleanup_errors() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// Create a file in the temp directory
fs::write(path.join("test.txt"), "data").unwrap();
// On drop, TempDir calls remove_dir_all
// This can fail if:
// - Directory doesn't exist (already deleted)
// - Permission denied
// - File system errors
// The error is logged but not propagated
// Drop can't return Result
// For explicit error handling, use close():
let temp_dir2 = tempdir().unwrap();
if let Err(e) = temp_dir2.close() {
eprintln!("Failed to clean up temp dir: {}", e);
// Handle error appropriately
}
}Drop ignores errors; close() allows error handling.
use tempfile::TempDir;
use std::path::PathBuf;
struct Workspace {
temp_dir: TempDir,
output_path: PathBuf,
}
impl Workspace {
fn new() -> std::io::Result<Self> {
let temp_dir = TempDir::new()?;
let output_path = temp_dir.path().join("output");
Ok(Self {
temp_dir,
output_path,
})
}
// When Workspace is dropped:
// 1. Workspace's Drop runs (if implemented)
// 2. temp_dir's Drop runs
// 3. Temp directory is cleaned up
}
fn workspace_usage() {
{
let workspace = Workspace::new().unwrap();
// Use workspace...
// workspace goes out of scope
// temp_dir inside is dropped
// Cleanup happens automatically
}
// Temp directory no longer exists
}Embedding TempDir in structs propagates automatic cleanup.
use tempfile::tempdir;
fn nested_temp_dirs() {
let outer = tempdir().unwrap();
let inner = tempdir().unwrap();
// Both temp directories exist
// inner goes out of scope first (LIFO order)
// inner is cleaned up
// outer goes out of scope second
// outer is cleaned up
// Order is guaranteed by Rust's drop order
}Destructors run in reverse order of creation, ensuring correct cleanup.
use tempfile::{tempdir, NamedTempFile};
fn temp_dir_vs_file() {
// TempDir: cleans up directory and all contents
let temp_dir = tempdir().unwrap();
// Creates a directory, cleans up everything on drop
// NamedTempFile: cleans up single file
let temp_file = NamedTempFile::new().unwrap();
// Creates a file, removes it on drop
// Both use RAII pattern
// Both implement Drop
// Both clean up on unwinding
}Both TempDir and NamedTempFile use the same RAII cleanup pattern.
use tempfile::tempdir;
use std::fs;
fn persist_directory() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// Create important data in temp directory
fs::write(path.join("important.txt"), "data").unwrap();
// If you want to keep the directory:
// 1. Use into_path() to take ownership
// 2. Or move important files out before drop
let persistent_path = temp_dir.into_path();
// Now responsible for cleanup manually
// Or keep it permanently
}Use into_path() when temporary data should become permanent.
// RAII = Resource Acquisition Is Initialization
// Key principle: resource lifetime is tied to object lifetime
// For TempDir:
// 1. Resource acquisition: creating the temp directory (in new())
// 2. Resource tied to object: TempDir holds the path
// 3. Resource release: removing directory (in Drop)
// Guarantees:
// - Directory is created when TempDir is created
// - Directory exists while TempDir exists
// - Directory is removed when TempDir is dropped
// - Drop happens on:
// - Normal scope exit
// - Early return
// - Panic (with unwinding)
// This is the same pattern used by:
// - Box (memory deallocation)
// - Vec (memory deallocation)
// - File (close file handle)
// - MutexGuard (unlock mutex)
// - RefCell borrow tracking
fn raii_guarantee() {
// The RAII pattern is the foundation of Rust's safety guarantees
// TempDir is just one example of this pattern
// The key insight: if cleanup is in Drop,
// and Drop runs on unwinding,
// then cleanup happens even on panic
}RAII ties resource cleanup to object lifetime, guaranteeing cleanup on scope exit.
use tempfile::TempDir;
use std::path::PathBuf;
// Simplified implementation concept:
//
// pub struct TempDir {
// path: PathBuf,
// }
//
// impl TempDir {
// pub fn new() -> io::Result<Self> {
// // Create unique directory in temp location
// let path = create_temp_directory()?;
// Ok(Self { path })
// }
//
// pub fn path(&self) -> &Path {
// &self.path
// }
//
// pub fn close(self) -> io::Result<()> {
// self.cleanup()
// }
//
// fn cleanup(&self) -> io::Result<()> {
// std::fs::remove_dir_all(&self.path)
// }
//
// pub fn into_path(self) -> PathBuf {
// // Take path without cleanup
// let path = std::mem::take(&mut self.path);
// std::mem::forget(self); // Don't run Drop
// path
// }
// }
//
// impl Drop for TempDir {
// fn drop(&mut self) {
// // Ignore errors during cleanup
// let _ = self.cleanup();
// }
// }
fn implementation_details() {
// Key methods:
// - new(): create directory
// - path(): access path
// - close(): cleanup with error handling
// - into_path(): keep directory
// - drop(): cleanup automatically
}The implementation is straightforward: Drop calls remove_dir_all.
use tempfile::tempdir;
use std::fs;
fn test_with_tempdir() -> std::io::Result<()> {
// Create isolated test environment
let temp_dir = tempdir()?;
// Test operations on temp directory
let file_path = temp_dir.path().join("test.txt");
fs::write(&file_path, "test data")?;
let contents = fs::read_to_string(&file_path)?;
assert_eq!(contents, "test data");
// Test directory operations
let subdir = temp_dir.path().join("subdir");
fs::create_dir(&subdir)?;
assert!(subdir.exists());
// Cleanup is automatic
// No need to remove files manually
// No need to worry about test pollution
Ok(())
}
#[test]
fn test_file_operations() {
let temp_dir = tempdir().unwrap();
// Run test with isolated environment
// Each test gets its own temp directory
// Cleanup is guaranteed after test
}TempDir is ideal for tests: automatic cleanup prevents test pollution.
use tempfile::tempdir;
fn multiple_temp_dirs() {
let temp1 = tempdir().unwrap();
let temp2 = tempdir().unwrap();
// Different directories
assert_ne!(temp1.path(), temp2.path());
// Both are cleaned up on drop
// Order: temp2 first, then temp1 (reverse order)
// Use cases:
// - Separate input and output directories
// - Multiple isolated workspaces
// - Different configurations
}Multiple temp directories are independent and all cleaned up on drop.
use std::fs;
use std::io;
// Without RAII: manual cleanup required
fn manual_temp_dir() -> io::Result<()> {
let temp_path = create_temp_directory_manually()?;
// Use temp directory...
// Must remember to clean up
// Cleanup might be skipped on error
fs::remove_dir_all(&temp_path)?;
// Problem: if an error occurs above, cleanup is skipped
// Solution: use explicit drop or scope
Ok(())
}
fn manual_with_scope() -> io::Result<()> {
let temp_path = create_temp_directory_manually()?;
// Use scope to ensure cleanup
let result = {
// Work in temp directory
// If this panics, cleanup doesn't happen!
do_work(&temp_path)?
};
fs::remove_dir_all(&temp_path)?;
Ok(result)
}
// With RAII: automatic cleanup
fn with_raii() -> io::Result<()> {
let temp_dir = tempfile::tempdir()?;
// Use temp directory...
// If this panics, cleanup still happens!
do_work(temp_dir.path())?;
// Cleanup is automatic
// No need for explicit cleanup
// No forgotten cleanup on errors
Ok(())
}
fn create_temp_directory_manually() -> io::Result<std::path::PathBuf> {
let temp_dir = std::env::temp_dir().join(format!("myapp-{}", uuid::Uuid::new_v4()));
fs::create_dir(&temp_dir)?;
Ok(temp_dir)
}
fn do_work(_path: &std::path::Path) -> io::Result<()> {
Ok(())
}RAII eliminates the risk of forgotten cleanup.
How tempdir ensures cleanup:
// 1. RAII Pattern: TempDir holds the directory path
// 2. Drop Implementation: calls remove_dir_all on drop
// 3. Stack Allocation: TempDir is on the stack, dropped on scope exit
// 4. Panic Unwinding: destructors run during unwinding
// Cleanup guarantees:
// - Normal return: Drop runs, cleanup happens
// - Early return: Drop runs, cleanup happens
// - Panic (unwind): Drop runs during unwinding, cleanup happens
// - Panic (abort): No unwinding, cleanup doesn't run (leak)
// - Process exit: No destructors, cleanup doesn't run (leak)
// - SIGKILL: Immediate termination, cleanup doesn't run (leak)What cleanup does NOT cover:
// Scenarios where cleanup fails:
// 1. panic = "abort" in Cargo.toml
// 2. std::process::exit()
// 3. Process killed externally (SIGKILL)
// 4. System crash or power failure
// 5. into_path() called (manual cleanup required)Key insight: tempfile::tempdir uses Rust's RAII pattern combined with panic unwinding to guarantee cleanup in all normal execution paths, including panics. The TempDir guard holds the directory path and implements Drop to remove the directory when the guard goes out of scope. During panic unwinding, Rust guarantees that destructors run for all stack-allocated values, so the TempDir is cleaned up even when the function panics. However, this guarantee only applies to unwinding panicsāabort panics, std::process::exit(), and external process termination bypass destructors entirely, causing the temporary directory to leak. For cases where cleanup must be explicit or errors must be handled, close() provides manual cleanup with error handling, and into_path() transfers cleanup responsibility to the caller when persistence is needed.