Loading pageā¦
Rust walkthroughs
Loading pageā¦
tempfile::TempDir ensure cleanup on panic scenarios?tempfile::TempDir ensures cleanup through Rust's RAII (Resource Acquisition Is Initialization) pattern combined with Drop trait implementationāthe directory is deleted when the TempDir value goes out of scope, whether through normal execution or unwinding during a panic. The Drop implementation removes the temporary directory and all its contents via std::fs::remove_dir_all, ensuring cleanup happens even when the stack unwinds due to a panic. This guarantee holds for all panic scenarios except aborts (where the process terminates immediately without unwinding), external process kills, or system crashesācases where no Rust code can execute. The TempDir type also handles edge cases like the directory being deleted externally before the Drop runs, making cleanup robust and idempotent.
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());
// Use the directory
let file_path = temp_dir.path().join("test.txt");
let mut file = File::create(&file_path)?;
file.write_all(b"Hello, temp!")?;
// Directory and contents are cleaned up when temp_dir goes out of scope
Ok(())
}The TempDir creates a directory that is automatically removed when it goes out of scope.
use tempfile::TempDir;
fn main() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
println!("Created: {:?}", path);
println!("Exists before drop: {}", path.exists());
// Drop the TempDir explicitly
drop(temp_dir);
println!("Exists after drop: {}", path.exists());
}The Drop implementation removes the directory and all contents when the value is dropped.
use tempfile::TempDir;
use std::panic;
fn main() {
let result = panic::catch_unwind(|| {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
println!("Created temp dir: {:?}", path);
println!("Exists: {}", path.exists());
// Simulate a panic while TempDir is in scope
panic!("intentional panic");
// temp_dir still gets cleaned up during unwinding
});
// After catch_unwind, the directory is cleaned up
match result {
Ok(_) => println!("No panic"),
Err(_) => println!("Panic caught, but temp dir was cleaned up"),
}
}During panic unwinding, Drop implementations still run, ensuring cleanup.
use tempfile::TempDir;
fn nested_operation() {
let outer_dir = TempDir::new().unwrap();
println!("Outer dir: {:?}", outer_dir.path());
{
let inner_dir = TempDir::new().unwrap();
println!("Inner dir: {:?}", inner_dir.path());
// Panic happens here
panic!("Panic in nested scope");
// inner_dir is dropped during unwinding
}
// outer_dir is dropped during unwinding
}
fn main() {
let result = std::panic::catch_unwind(|| {
nested_operation();
});
println!("Caught panic, both dirs cleaned up");
}Nested TempDir values are cleaned up in reverse order of creation during unwinding.
use tempfile::TempDir;
use std::fs;
fn main() {
// TempDir's Drop implementation is robust
// It handles the case where the directory doesn't exist
// (already deleted externally)
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
// Manually delete the directory before Drop runs
drop(temp_dir);
// Now create another and verify it handles already-deleted
let temp_dir2 = TempDir::new().unwrap();
// If we manually delete it:
fs::remove_dir_all(temp_dir2.path()).unwrap();
// Drop still runs, but silently succeeds (idempotent)
drop(temp_dir2);
println!("Drop handled missing directory gracefully");
}The Drop implementation is idempotentāsafe to call even if directory was already deleted.
use tempfile::TempDir;
fn main() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
println!("Temp dir: {:?}", path);
println!("Temp dir exists: {}", path.exists());
// If we abort (not unwind), Drop doesn't run
// std::process::abort();
// Uncommenting the above would:
// 1. Terminate immediately without unwinding
// 2. NOT call Drop implementations
// 3. Leave the temp directory on disk
println!("Normal exit - temp dir will be cleaned up");
}std::process::abort() bypasses Drop, leaving temporary files behind.
use tempfile::TempDir;
use std::path::Path;
fn main() -> std::io::Result<()> {
// Create temp dir in specific location
let temp_dir = TempDir::new_in("/tmp/myapp")?;
println!("Created in custom location: {:?}", temp_dir.path());
// Create with prefix
let temp_dir_with_prefix = TempDir::with_prefix("myapp-")?;
println!("With prefix: {:?}", temp_dir_with_prefix.path());
// Create with prefix in specific location
let temp_dir_custom = TempDir::with_prefix_in("myapp-", "/tmp")?;
println!("Custom prefix+location: {:?}", temp_dir_custom.path());
Ok(())
}Control where temporary directories are created.
use tempfile::TempDir;
use std::path::PathBuf;
fn main() -> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let path = temp_dir.path().to_path_buf();
// Keep the directory after TempDir is dropped
let preserved_path = temp_dir.into_path();
println!("Preserved path: {:?}", preserved_path);
// Now temp_dir is consumed, path won't be deleted
// The directory persists after this scope
// Check it still exists
println!("Still exists: {}", preserved_path.exists());
// Manual cleanup needed for preserved path
std::fs::remove_dir_all(&preserved_path)?;
Ok(())
}into_path() consumes the TempDir and prevents automatic cleanup.
use tempfile::TempDir;
use std::time::Duration;
use std::thread;
fn main() {
// TempDir cleanup is synchronous and deterministic
fn test_cleanup_timing() {
let start = std::time::Instant::now();
{
let temp_dir = TempDir::new().unwrap();
// Create some files
for i in 0..1000 {
let path = temp_dir.path().join(format!("file{}.txt", i));
std::fs::write(&path, "data").unwrap();
}
// Scope ends here
} // Drop runs immediately and synchronously
let elapsed = start.elapsed();
println!("Cleanup took: {:?}", elapsed);
}
test_cleanup_timing();
// Cleanup is blocking - happens in the same thread
// No background threads, no async cleanup
}Cleanup is synchronous and blocking when the TempDir is dropped.
use tempfile::TempDir;
use std::fs;
fn manual_cleanup_example() -> std::io::Result<()> {
// Manual approach - error-prone
let manual_dir = fs::create_dir_all("/tmp/myapp-manual")?;
// Risk: if function returns early, directory leaks
if some_condition() {
// Oops - forgot cleanup
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"early return"
));
}
// Risk: if panic happens, directory leaks
// (unless catch_unwind is used)
fs::remove_dir_all("/tmp/myapp-manual")?;
Ok(())
}
fn tempdir_automated() -> std::io::Result<()> {
// Automatic approach - cleanup guaranteed
let temp_dir = TempDir::new()?;
if some_condition() {
// Cleanup still happens via Drop
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"early return"
));
}
// Panic would also trigger cleanup
Ok(())
// Cleanup happens here
}
fn some_condition() -> bool {
false
}TempDir guarantees cleanup across all exit paths, unlike manual approaches.
use tempfile::TempDir;
fn main() {
// TempDir's Drop ignores errors during cleanup
// This is intentional: panicking in Drop is problematic
// (double panic = abort)
// If cleanup fails (e.g., permission denied), it's silently ignored
// This prevents Drop from panicking
let temp_dir = TempDir::new().unwrap();
// If we manually cause cleanup issues:
// - Directory deleted externally: cleanup silently succeeds (no-op)
// - Permission denied: cleanup fails silently
// - Disk error: cleanup fails silently
// This design choice prioritizes program stability over cleanup guarantees
drop(temp_dir);
println!("Drop completed (even if cleanup had issues)");
}Drop silently ignores cleanup errors to avoid double panics.
use tempfile::TempDir;
use std::thread;
fn main() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
// TempDir is not Clone or Copy
// It must be moved or its path shared
// Share the path across threads
let handles: Vec<_> = (0..4)
.map(|i| {
let dir_path = path.clone();
thread::spawn(move || {
let file_path = dir_path.join(format!("thread{}.txt", i));
std::fs::write(&file_path, format!("Thread {} data", i)).unwrap();
println!("Thread {} wrote file", i);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
// TempDir is still in scope in main thread
// Cleanup happens when main thread's temp_dir is dropped
println!("Main thread still owns TempDir");
drop(temp_dir);
println!("All cleaned up");
}TempDir ownership stays in one thread; share paths, not the TempDir itself.
use tempfile::TempDir;
use std::thread;
use std::panic;
fn main() {
let result = thread::spawn(|| {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
println!("Thread created temp dir: {:?}", path);
// Panic in this thread
panic!("Thread panic");
// temp_dir is dropped during thread unwinding
}).join();
match result {
Ok(_) => println!("Thread completed"),
Err(_) => println!("Thread panicked, temp dir was cleaned up"),
}
// Each thread's TempDir is cleaned up when that thread panics
}Panic in a thread cleans up that thread's TempDir instances.
use tempfile::TempDir;
use std::ops::Deref;
// RAII wrapper that ensures cleanup
struct Workspace {
dir: TempDir,
}
impl Workspace {
fn new() -> std::io::Result<Self> {
let dir = TempDir::new()?;
println!("Workspace created at: {:?}", dir.path());
Ok(Workspace { dir })
}
fn path(&self) -> &std::path::Path {
self.dir.path()
}
// Explicit cleanup with error reporting
fn close(self) -> Result<(), std::io::Error> {
let path = self.dir.path().to_path_buf();
drop(self);
println!("Workspace closed: {:?}", path);
Ok(())
}
}
impl Drop for Workspace {
fn drop(&mut self) {
println!("Workspace cleanup at: {:?}", self.dir.path());
}
}
fn main() -> std::io::Result<()> {
{
let workspace = Workspace::new()?;
// Use workspace...
// Cleanup happens when workspace goes out of scope
}
// Or explicit close
let workspace2 = Workspace::new()?;
workspace2.close()?;
Ok(())
}TempDir integrates with RAII patterns for custom workspace management.
use tempfile::TempDir;
use std::fs;
use std::path::Path;
fn process_files(input_dir: &Path, output_dir: &Path) -> std::io::Result<()> {
// Process all files in input_dir, write to output_dir
for entry in fs::read_dir(input_dir)? {
let entry = entry?;
let input_path = entry.path();
if input_path.extension().map_or(false, |e| e == "txt") {
let content = fs::read_to_string(&input_path)?;
let processed = content.to_uppercase();
let output_path = output_dir.join(entry.file_name());
fs::write(&output_path, processed)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_process_files() {
// Create temporary directories for test
let input_dir = TempDir::new().unwrap();
let output_dir = TempDir::new().unwrap();
// Setup test input
fs::write(input_dir.path().join("file1.txt"), "hello").unwrap();
fs::write(input_dir.path().join("file2.txt"), "world").unwrap();
// Run test
process_files(input_dir.path(), output_dir.path()).unwrap();
// Verify output
let output1 = fs::read_to_string(output_dir.path().join("file1.txt")).unwrap();
let output2 = fs::read_to_string(output_dir.path().join("file2.txt")).unwrap();
assert_eq!(output1, "HELLO");
assert_eq!(output2, "WORLD");
// Cleanup happens automatically when test ends
// Even if test panics
}
#[test]
fn test_with_panic() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("test.txt"), "data").unwrap();
// Panic here - temp_dir still gets cleaned up
panic!("Test panic");
}
}TempDir is ideal for test isolation with automatic cleanup.
use tempfile::TempDir;
fn main() {
// TempDir handles resource exhaustion during cleanup
// If disk is full:
// - Cleanup still attempts to delete
// - Failure is silent (can't report errors from Drop)
// If too many files:
// - remove_dir_all handles large directories
// - But may take time and slow down Drop
// Best practices:
// 1. Don't create excessive files in temp dirs
// 2. Clean up explicitly if you need error handling
// 3. Use into_path() for debugging to inspect what's left
let temp_dir = TempDir::new().unwrap();
// Create many files
for i in 0..100 {
let path = temp_dir.path().join(format!("file{}", i));
std::fs::write(&path, "data").unwrap();
}
// Drop will clean up all files
// This is O(n) where n = number of files + directories
}Cleanup time scales with directory size; clean up explicitly for error handling.
use tempfile::TempDir;
use std::panic;
fn main() {
let result = panic::catch_unwind(|| {
let temp_dir = TempDir::new().unwrap();
// Create some files
std::fs::write(temp_dir.path().join("test.txt"), "data").unwrap();
// Convert to persistent path for debugging
let persistent_path = temp_dir.into_path();
println!("Path preserved for inspection: {:?}", persistent_path);
// Now panic won't clean up the directory
panic!("Debug panic");
// Directory persists at persistent_path
});
// Check if path still exists
// (In real code, we'd capture persistent_path outside the closure)
println!("Result: {:?}", result.is_err());
}into_path() prevents cleanup for debugging or intentional persistence.
use tempfile::TempDir;
fn main() {
// TempDir's Drop implementation:
// 1. Calls std::fs::remove_dir_all(self.path())
// 2. Ignores the Result (silently handles errors)
// 3. Does not panic (prevents double panic)
// This means:
// - Directory is removed with all contents
// - Errors are swallowed (can't report them)
// - Safe to call during panic unwinding
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
// Verify it exists
println!("Before drop: exists = {}", path.exists());
// Drop calls remove_dir_all
drop(temp_dir);
// Verify it's gone
println!("After drop: exists = {}", path.exists());
// Dropping again (if we had another reference) would be safe
// because Drop is idempotent
}The Drop implementation uses remove_dir_all and ignores errors.
use tempfile::{TempDir, NamedTempFile};
fn main() -> std::io::Result<()> {
// TempDir: directory cleanup
let dir = TempDir::new()?;
// NamedTempFile: individual file cleanup
let file = NamedTempFile::new()?;
// Both use Drop for cleanup
// Both guarantee cleanup on panic
// Both use std::fs operations internally
// TempDir cleans up directory and all contents
// NamedTempFile cleans up just one file
// Both support into_path() / into_temp_path() to persist
// Use TempDir for multiple files
// Use NamedTempFile for single file
Ok(())
}TempDir and NamedTempFile share the same cleanup philosophy but different scopes.
use tempfile::TempDir;
use std::fs;
use std::path::Path;
fn process_pipeline(input_files: &[&str]) -> std::io::Result<Vec<String>> {
// Create workspace for intermediate files
let workspace = TempDir::new()?;
// Stage 1: Copy inputs
let stage1_dir = workspace.path().join("stage1");
fs::create_dir(&stage1_dir)?;
for (i, input) in input_files.iter().enumerate() {
let dest = stage1_dir.join(format!("input{}.txt", i));
fs::write(&dest, input)?;
}
// Stage 2: Process
let stage2_dir = workspace.path().join("stage2");
fs::create_dir(&stage2_dir)?;
for entry in fs::read_dir(&stage1_dir)? {
let entry = entry?;
let content = fs::read_to_string(entry.path())?;
let processed = content.to_uppercase();
let dest = stage2_dir.join(entry.file_name());
fs::write(&dest, processed)?;
}
// Stage 3: Final output
let mut results = Vec::new();
for entry in fs::read_dir(&stage2_dir)? {
let entry = entry?;
let content = fs::read_to_string(entry.path())?;
results.push(content);
}
// workspace is cleaned up when function returns
// Even if there's a panic during processing
Ok(results)
}
fn main() -> std::io::Result<()> {
let inputs = vec!["hello", "world", "test"];
let results = process_pipeline(&inputs)?;
for result in results {
println!("Result: {}", result);
}
Ok(())
}TempDir provides automatic workspace cleanup for multi-stage processing.
use tempfile::TempDir;
use std::fs;
use std::path::PathBuf;
struct TestFixture {
temp_dir: TempDir,
config_path: PathBuf,
data_path: PathBuf,
}
impl TestFixture {
fn new() -> std::io::Result<Self> {
let temp_dir = TempDir::new()?;
// Setup config
let config_path = temp_dir.path().join("config.json");
fs::write(&config_path, r#"{"debug": true}"#)?;
// Setup data directory
let data_path = temp_dir.path().join("data");
fs::create_dir(&data_path)?;
// Setup initial data files
fs::write(data_path.join("users.json"), "[]")?;
fs::write(data_path.join("items.json"), "[]")?;
Ok(TestFixture {
temp_dir,
config_path,
data_path,
})
}
fn root(&self) -> &std::path::Path {
self.temp_dir.path()
}
fn config(&self) -> &std::path::Path {
&self.config_path
}
fn data(&self) -> &std::path::Path {
&self.data_path
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_fixture() {
let fixture = TestFixture::new().unwrap();
// Verify setup
assert!(fixture.config().exists());
assert!(fixture.data().exists());
// Do test work
let config_content = fs::read_to_string(fixture.config()).unwrap();
assert!(config_content.contains("debug"));
// Cleanup happens when fixture goes out of scope
// Even if panic occurs
}
#[test]
fn test_multiple_fixtures() {
// Each test gets isolated directories
let fixture1 = TestFixture::new().unwrap();
let fixture2 = TestFixture::new().unwrap();
assert_ne!(fixture1.root(), fixture2.root());
// Both are cleaned up independently
}
}TempDir enables isolated test fixtures with automatic cleanup.
Cleanup guarantee levels:
| Scenario | Cleanup guaranteed? | Reason |
|----------|---------------------|--------|
| Normal return | ā
Yes | Drop runs normally |
| Panic (unwinding) | ā
Yes | Drop runs during unwinding |
| Early return | ā
Yes | Drop runs at scope exit |
| std::process::abort() | ā No | No unwinding, Drop skipped |
| External process kill | ā No | Process terminated, no code runs |
| System crash | ā No | No process execution |
| Power failure | ā No | No process execution |
Key methods:
| Method | Purpose | Cleanup? |
|--------|---------|----------|
| TempDir::new() | Create temp dir | Automatic on drop |
| TempDir::new_in() | Create in specific location | Automatic on drop |
| TempDir::path() | Get path reference | Automatic on drop |
| TempDir::into_path() | Persist directory | Manual cleanup needed |
Drop implementation behavior:
| Situation | Result | |-----------|--------| | Directory exists | Deleted with contents | | Directory deleted externally | Silently succeeds | | Permission denied | Silently fails | | Disk error | Silently fails |
Key insight: tempfile::TempDir ensures cleanup through Rust's RAII patternāthe Drop implementation calls std::fs::remove_dir_all when the TempDir goes out of scope, whether through normal execution or panic unwinding. This guarantee applies to all panic scenarios where unwinding occurs, but not to aborts or external process termination where no Rust code can execute. The Drop implementation is idempotent (safe if directory already deleted) and error-ignoring (won't panic if cleanup fails), prioritizing program stability over cleanup error reporting. For cases where you need explicit control over cleanup timing or error handling, use into_path() to prevent automatic cleanup and manage the directory manually. The synchronous, blocking nature of cleanup means it happens deterministically at scope exit, making TempDir ideal for test isolation, file processing pipelines, and any scenario where temporary workspace cleanup must be guaranteed across all exit paths.