Loading pageā¦
Rust walkthroughs
Loading pageā¦
tempfile::tempdir ensure cleanup even if your code panics?tempfile::tempdir uses Rust's RAII (Resource Acquisition Is Initialization) pattern combined with the Drop trait to guarantee cleanup. When you call tempdir(), it creates a temporary directory and returns a TempDir guard object. This guard's Drop implementation removes the directory and all its contents, and Rust guarantees that Drop::drop runs when the guard goes out of scopeāregardless of whether the scope exits normally or via a panic. The cleanup mechanism relies on stack unwinding during panics: as the stack unwinds, all local variables are dropped in reverse order of their construction, ensuring the TempDir guard cleans up even during error conditions.
use tempfile::tempdir;
use std::fs::File;
use std::io::Write;
fn basic_usage() -> Result<(), std::io::Error> {
// Create a temporary directory
let temp_dir = tempdir()?;
println!("Temp directory: {:?}", temp_dir.path());
// Create files inside the temp directory
let file_path = temp_dir.path().join("test.txt");
let mut file = File::create(&file_path)?;
file.write_all(b"Hello, temp!")?;
// temp_dir goes out of scope here
// Directory is automatically deleted
Ok(())
}The TempDir guard automatically cleans up when it goes out of scope.
use tempfile::TempDir;
use std::path::PathBuf;
fn drop_mechanism() {
// TempDir is a RAII guard
let temp_dir = tempfile::tempdir().unwrap();
let path: PathBuf = temp_dir.path().to_path_buf();
println!("Directory exists: {}", path.exists()); // true
// When temp_dir is dropped, the directory is removed
drop(temp_dir);
println!("Directory exists: {}", path.exists()); // false
}The Drop trait implementation removes the directory when the TempDir is dropped.
use tempfile::tempdir;
use std::fs;
fn panic_safety() {
let path;
{
let temp_dir = tempdir().unwrap();
path = temp_dir.path().to_path_buf();
// Create some files
fs::write(temp_dir.path().join("data.txt"), "important data").unwrap();
println!("Before panic, dir exists: {}", path.exists());
// Panic occurs
panic!("Something went wrong!");
// temp_dir is still dropped during stack unwinding
}
// This line is never reached due to panic
}
fn catch_panic_cleanup() {
let result = std::panic::catch_unwind(|| {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
fs::write(path.join("data.txt"), "test").unwrap();
panic!("Intentional panic");
});
// Even though we caught the panic, the temp dir was cleaned up
assert!(result.is_err());
}During panic-induced stack unwinding, Drop implementations still run.
// Simplified view of tempfile's internal structure
use std::path::PathBuf;
use std::fs;
pub struct TempDir {
path: PathBuf,
}
impl Drop for TempDir {
fn drop(&mut self) {
// Remove the directory and all its contents
let _ = fs::remove_dir_all(&self.path);
// Errors are ignored in drop (can't propagate them)
}
}
fn demonstrate_internal() {
// The actual tempfile crate is more sophisticated:
// - It handles edge cases like the directory being deleted already
// - It has configurable cleanup behavior
// - It handles permissions issues
// But the core mechanism is simple: Drop removes the directory
}The Drop implementation calls remove_dir_all to clean up the directory.
use tempfile::tempdir;
use std::fs::File;
fn cleanup_order() {
let temp_dir = tempdir().unwrap();
// Files inside the temp directory
let file1 = File::create(temp_dir.path().join("file1.txt")).unwrap();
let file2 = File::create(temp_dir.path().join("file2.txt")).unwrap();
// Drop order: file2, file1, temp_dir
// Files are closed (dropped) before directory is removed
// This is correct: you can't remove a directory with open files on some OSes
}
fn explicit_ordering() {
let temp_dir = tempdir().unwrap();
// If you need explicit control, drop files first
{
let file = File::create(temp_dir.path().join("file.txt")).unwrap();
// Use file...
} // File dropped here
// temp_dir dropped later, can safely remove
}Rust's reverse-order drop ensures files are closed before their containing directory is removed.
use tempfile::tempdir;
use std::path::PathBuf;
fn into_path_example() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// If you want to keep the directory, use into_path()
let preserved_path: PathBuf = temp_dir.into_path();
// Now the directory won't be deleted
// You're responsible for cleaning it up manually
println!("Preserved at: {:?}", preserved_path);
// You must clean it up yourself:
// fs::remove_dir_all(&preserved_path).unwrap();
}
fn conditional_preserve() -> Result<PathBuf, std::io::Error> {
let temp_dir = tempdir()?;
// Do some work...
let success = do_work(temp_dir.path())?;
if success {
// Keep the directory
Ok(temp_dir.into_path())
} else {
// Let it be cleaned up
Ok(temp_dir.path().to_path_buf())
}
// temp_dir dropped here if not consumed by into_path()
}
fn do_work(_path: &std::path::Path) -> Result<bool, std::io::Error> {
Ok(true)
}into_path() consumes the TempDir guard, preventing automatic cleanup.
use tempfile::tempdir;
use std::fs;
fn cleanup_errors() {
// Drop can't return errors, so cleanup errors are silently ignored
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// If something else deletes the directory first
drop(temp_dir); // remove_dir_all fails, error is ignored
// The tempfile crate handles this gracefully
}
fn manual_cleanup_with_errors() -> Result<(), std::io::Error> {
let temp_dir = tempdir()?;
let path = temp_dir.path().to_path_buf();
// Do work...
fs::write(path.join("data.txt"), "content")?;
// Manual cleanup with error handling
// close() consumes the TempDir and returns Result
temp_dir.close()?; // Returns error if cleanup fails
Ok(())
}Use close() instead of relying on Drop if you need to handle cleanup errors.
use tempfile::tempdir;
fn nested_temp_dirs() {
let outer = tempdir().unwrap();
let inner = tempdir().unwrap();
// These are separate temp directories
println!("Outer: {:?}", outer.path());
println!("Inner: {:?}", inner.path());
// Drop order: inner, then outer
// Each cleans up its own directory
}
fn nested_in_hierarchy() {
let outer = tempdir().unwrap();
// Create a temp dir inside another temp dir
let inner_path = outer.path().join("inner");
std::fs::create_dir(&inner_path).unwrap();
let inner = tempfile::tempdir_in(&inner_path).unwrap();
// Drop order: inner, then outer
// Inner's directory is inside outer's directory
// When outer is dropped, remove_dir_all removes everything
}Nested temp directories are cleaned up in reverse order of creation.
use tempfile::tempdir;
use std::sync::Arc;
use std::thread;
fn concurrent_temp_dirs() {
// Each thread gets its own temp directory
let handles: Vec<_> = (0..4)
.map(|i| {
thread::spawn(move || {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// Each thread has its own isolated temp directory
std::fs::write(path.join("thread.txt"), format!("Thread {}", i)).unwrap();
// Work...
println!("Thread {} using {:?}", i, path);
// temp_dir cleaned up when thread exits
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
fn shared_temp_dir() {
// Shared temp directory with Arc
let temp_dir = Arc::new(tempdir().unwrap());
let handles: Vec<_> = (0..4)
.map(|i| {
let dir = Arc::clone(&temp_dir);
thread::spawn(move || {
let path = dir.path().join(format!("thread_{}.txt", i));
std::fs::write(path, format!("Thread {}", i)).unwrap();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
// Arc<TempDir> cleanup: when last Arc is dropped
// Note: TempDir inside Arc still cleans up on drop
// But be careful: cleanup happens when the LAST Arc is dropped
}Each TempDir is independent; sharing one requires careful lifetime management.
use tempfile::tempdir;
use tokio::fs;
async fn async_tempdir() -> Result<(), std::io::Error> {
let temp_dir = tempdir()?;
// Async file operations inside the temp directory
let file_path = temp_dir.path().join("async_data.txt");
fs::write(&file_path, b"async content").await?;
// Read it back
let content = fs::read_to_string(&file_path).await?;
println!("Read: {}", content);
// temp_dir cleans up when dropped
// Note: this is synchronous cleanup, not async
Ok(())
}
async fn temp_dir_in_async_context() {
// TempDir works fine in async contexts
async fn process() -> Result<(), std::io::Error> {
let temp_dir = tempdir()?;
// Do async work
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
// Temp dir still cleaned up on drop
Ok(())
}
process().await.unwrap();
}TempDir is synchronous; cleanup happens synchronously when the guard is dropped.
use tempfile::Builder;
fn configured_temp_dir() {
// Use Builder for more control
let temp_dir = Builder::new()
.prefix("myapp_")
.suffix("_temp")
.rand_bytes(8)
.tempdir()
.unwrap();
println!("Named temp dir: {:?}", temp_dir.path());
// Path like: /tmp/myapp_X7Y8Z9W0_temp
// Temp directory in specific location
let temp_dir = Builder::new()
.prefix("cache_")
.tempdir_in("./cache")
.unwrap();
println!("Cache temp: {:?}", temp_dir.path());
}
fn temp_dir_in_existing() {
// Create temp directory in a specific parent directory
let parent = std::path::Path::new("./my_temp");
std::fs::create_dir_all(parent).unwrap();
let temp_dir = tempfile::tempdir_in(parent).unwrap();
println!("In parent: {:?}", temp_dir.path());
}Builder provides control over naming and location of temporary directories.
use tempfile::tempdir;
use std::panic;
fn panic_twophase() {
// Panic causes stack unwinding
// All locals with Drop implementations are dropped
let result = panic::catch_unwind(|| {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
fs::write(path.join("data.txt"), "test").unwrap();
panic!("Intentional panic!");
// Stack unwinding occurs here:
// 1. temp_dir's Drop runs
// 2. Directory is removed
// 3. Unwinding continues
});
assert!(result.is_err());
}
fn panic_abort_caveat() {
// If panics are set to abort (panic = "abort" in Cargo.toml)
// Stack unwinding does NOT occur
// TempDir's Drop does NOT run
// The temporary directory remains on disk
// This is a fundamental limitation: abort means no cleanup
}tempdir cleanup relies on stack unwinding; panic = abort prevents cleanup.
use std::path::PathBuf;
use std::fs;
// Manual cleanup is error-prone
fn manual_temp_dir() -> Result<(), std::io::Error> {
let path = PathBuf::from("/tmp/my_temp_123");
fs::create_dir(&path)?;
// Do work...
let result = do_risky_work(&path);
// Must remember to clean up in all paths
if result.is_err() {
fs::remove_dir_all(&path)?; // Easy to forget
return result;
}
fs::remove_dir_all(&path)?; // Duplicate cleanup code
Ok(())
}
fn do_risky_work(_path: &PathBuf) -> Result<(), std::io::Error> {
Ok(())
}
// With TempDir, cleanup is automatic
fn auto_temp_dir() -> Result<(), std::io::Error> {
let temp_dir = tempfile::tempdir()?;
// Do work...
do_risky_work(&temp_dir.path().to_path_buf())?;
// Cleanup happens automatically via Drop
// Works for early returns, errors, panics
Ok(())
}RAII with Drop ensures cleanup without manual error handling for every path.
use tempfile::tempdir;
fn drop_guarantee() {
// Rust guarantees that Drop runs when:
// 1. Variable goes out of scope normally
// 2. Variable goes out of scope due to early return
// 3. Variable goes out of scope during stack unwinding (panic)
// The only exception is when the program aborts
// (panic = "abort", or explicit std::process::abort)
let temp_dir = tempdir().unwrap();
// These all trigger cleanup:
// - Normal scope exit
// - return
// - ?
// - panic!
// - break/continue (if in a loop)
}
fn early_return_cleanup() -> Result<(), std::io::Error> {
let temp_dir = tempdir()?;
let data = std::fs::read_to_string(temp_dir.path().join("config.txt"));
// If this returns early, temp_dir is still cleaned up
// Check for file existence
if !temp_dir.path().join("required.txt").exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"required file missing"
));
// temp_dir still cleaned up
}
Ok(())
}Rust's Drop guarantee ensures cleanup in nearly all scenarios.
use tempfile::tempdir;
use std::path::Path;
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_file_operations() {
// Each test gets a fresh, isolated temp directory
let temp_dir = tempdir().unwrap();
// Test operations
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "test content").unwrap();
let content = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "test content");
// Cleanup happens automatically
// No pollution between tests
}
#[test]
fn test_another_isolation() {
// Different temp directory, isolated from other tests
let temp_dir = tempdir().unwrap();
// Previous test's files don't exist here
assert!(!temp_dir.path().join("test.txt").exists());
}
#[test]
fn test_with_panic() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().to_path_buf();
// Even if test panics, cleanup occurs
// panic!("Test failed");
// temp_dir still cleaned up during stack unwinding
}
}tempdir is ideal for test isolation with automatic cleanup.
use tempfile::tempdir;
use std::process::Command;
fn process_files(input: &Path, output: &Path) -> Result<(), std::io::Error> {
// Create staging area
let staging = tempdir()?;
// Step 1: Extract to staging
extract_files(input, staging.path())?;
// Step 2: Process files
process_in_place(staging.path())?;
// Step 3: Package results
package_files(staging.path(), output)?;
// Staging cleaned up automatically
Ok(())
}
fn extract_files(_input: &Path, _staging: &Path) -> Result<(), std::io::Error> {
Ok(())
}
fn process_in_place(_staging: &Path) -> Result<(), std::io::Error> {
Ok(())
}
fn package_files(_staging: &Path, _output: &Path) -> Result<(), std::io::Error> {
Ok(())
}
use std::path::Path;Temporary directories are perfect for staging areas in processing pipelines.
| Scenario | Manual Cleanup | tempfile::tempdir |
|----------|----------------|---------------------|
| Normal exit | Easy to forget | Automatic |
| Early return | Must handle every path | Automatic |
| Panic | Often missed | Automatic (unwind) |
| Abort | N/A | Cannot clean up |
| Error reporting | Full control | Silent in Drop, use close() |
| Thread safety | Manual coordination | Per-thread isolation |
tempfile::tempdir ensures cleanup through Rust's RAII and Drop mechanisms:
How it works:
tempdir() creates a directory and returns a TempDir guardDrop::drop calls remove_dir_all on the pathdrop runs when the guard goes out of scopeWhy it's safe:
drop runs at end of scopedrop runs as stack unwindsTempDirLimitations:
panic = abort: no stack unwinding, no cleanupstd::process::exit: skips all destructorsstd::process::abort: immediate terminationclose() for error handling)Key insight: The cleanup guarantee comes from Rust's ownership system, not special magic. Any type implementing Drop gets the same guaranteeāTempDir simply applies this to directory cleanup. This pattern is why RAII is fundamental to Rust's safety story: the type system enforces cleanup without runtime overhead or programmer vigilance. The only way cleanup can fail is if the program terminates abnormally (abort), which by definition means cleanup is no longer relevant.