Loading pageā¦
Rust walkthroughs
Loading pageā¦
tempfile::tempdir ensure cleanup even in the presence of panics?tempfile::tempdir creates a temporary directory that automatically cleans itself up when the returned TempDir guard goes out of scope, leveraging Rust's RAII (Resource Acquisition Is Initialization) pattern and the Drop trait. The Drop implementation for TempDir removes the directory and all its contents, and Rust guarantees that Drop::drop is called during stack unwinding when a panic occurs. This ensures cleanup happens deterministically without manual intervention, even in error scenarios. The mechanism relies on Rust's panic runtime unwinding the stack and calling destructors for all live values in reverse order of their creation.
use tempfile::tempdir;
use std::fs::File;
use std::io::Write;
fn main() -> std::io::Result<()> {
// Create a temporary directory
let dir = tempdir()?;
// Use the directory
let file_path = dir.path().join("temp.txt");
let mut file = File::create(&file_path)?;
file.write_all(b"Hello, temp!")?;
// Directory is automatically cleaned up when `dir` goes out of scope
Ok(())
}tempdir() returns a TempDir that cleans up on drop.
use tempfile::TempDir;
// TempDir is a guard type - it holds ownership of the resource
// When dropped, it cleans up the resource
fn example() -> std::io::Result<()> {
let dir = TempDir::new()?;
// Resource acquired: temp directory created
// Resource held: dir is live
// Do work with the directory
let path = dir.path();
println!("Temp path: {:?}", path);
// When this function returns, dir is dropped
// Drop implementation removes the directory
Ok(())
}TempDir follows RAII: acquisition is initialization, release is destruction.
use std::fs;
use std::path::PathBuf;
// Simplified illustration of TempDir's Drop implementation
struct MyTempDir {
path: PathBuf,
}
impl Drop for MyTempDir {
fn drop(&mut self) {
// This is called when the guard goes out of scope
// Rust guarantees this runs during panic unwinding
let _ = fs::remove_dir_all(&self.path);
println!("Cleaned up: {:?}", self.path);
}
}
fn main() {
{
let _temp = MyTempDir {
path: std::env::temp_dir().join("example"),
};
// _temp is dropped at end of scope
}
println!("After scope");
}The Drop trait ensures cleanup runs when the guard is destroyed.
use tempfile::tempdir;
fn might_panic() {
let dir = tempdir().expect("Failed to create temp dir");
println!("Created: {:?}", dir.path());
// Simulate a panic
panic!("Something went wrong!");
// Even though we panic, dir.drop() is called
// The temporary directory is still cleaned up
}
fn main() {
let result = std::panic::catch_unwind(|| {
might_panic();
});
match result {
Err(_) => println!("Caught panic, temp dir was cleaned up"),
Ok(_) => println!("Completed successfully"),
}
}Panic unwinding calls Drop for all live values, including TempDir.
use tempfile::tempdir;
fn inner_function() {
let dir1 = tempdir().unwrap();
let dir2 = tempdir().unwrap();
panic!("Panicking with two temp dirs in scope");
// Cleanup order: dir2.drop(), then dir1.drop()
// Reverse order of construction (LIFO - stack order)
}
fn main() {
let result = std::panic::catch_unwind(|| {
inner_function();
});
if result.is_err() {
println!("Both temp directories were cleaned up");
}
}Stack unwinding calls destructors in reverse order of construction.
// Rust's default panic behavior is to unwind the stack
// This means Drop implementations are called during panic
use tempfile::tempdir;
fn demonstrate_unwind() {
let dir = tempdir().unwrap();
// This panic triggers unwinding
// During unwinding, dir's Drop is called
panic!("Unwinding in progress");
}
// If panic = "abort" in Cargo.toml, unwinding doesn't happen
// In that case, Drop is NOT called (program terminates immediately)Cleanup relies on unwind strategy; panic = "abort" skips Drop.
use tempfile::tempdir;
use std::path::PathBuf;
fn main() -> std::io::Result<()> {
let dir = tempdir()?;
println!("Temp path: {:?}", dir.path());
// Convert to PathBuf to prevent cleanup
let persistent_path: PathBuf = dir.into_path();
// Now the directory won't be cleaned up automatically
// It's the caller's responsibility to remove it
println!("Persistent path: {:?}", persistent_path);
// Directory still exists after program ends
Ok(())
}into_path() consumes the guard without cleanup, leaving the directory.
use tempfile::tempdir;
fn main() -> std::io::Result<()> {
let dir = tempdir?;
// Use the directory
let path = dir.path();
// Explicitly clean up before end of scope
dir.close()?; // Consumes dir and cleans up
// Can't use dir after close() - it's been moved
println!("Directory cleaned up explicitly");
Ok(())
}close() provides explicit cleanup with error handling.
use tempfile::tempdir;
// TempDir's Drop implementation handles cleanup errors:
// - On success: directory is removed
// - On error: error is logged to stderr (not propagated as panic)
// - Drop should not panic, so errors are suppressed
fn main() -> std::io::Result<()> {
let dir = tempdir()?;
// If cleanup fails (e.g., permission denied),
// the error is logged but doesn't cause additional panic
Ok(())
// dir.drop() is called here
}Drop implementations should not panic; errors are logged instead.
use tempfile::tempdir;
fn main() -> std::io::Result<()> {
let outer = tempdir()?;
let inner = tempdir()?;
// Create file in inner
std::fs::write(inner.path().join("data.txt"), b"test")?;
// Cleanup order on scope exit:
// 1. inner.drop() - removes inner and its contents
// 2. outer.drop() - removes outer
Ok(())
}Nested TempDir values clean up in reverse order.
use tempfile::TempDir;
struct Workspace {
dir: TempDir,
files: Vec<String>,
}
impl Workspace {
fn new() -> std::io::Result<Self> {
Ok(Workspace {
dir: TempDir::new()?,
files: Vec::new(),
})
}
fn add_file(&mut self, name: &str, content: &str) -> std::io::Result<()> {
let path = self.dir.path().join(name);
std::fs::write(path, content)?;
self.files.push(name.to_string());
Ok(())
}
}
impl Drop for Workspace {
fn drop(&mut self) {
// TempDir's Drop is called after this
// No additional cleanup needed - TempDir handles it
println!("Workspace dropping");
}
}
fn main() -> std::io::Result<()> {
{
let mut ws = Workspace::new()?;
ws.add_file("config.txt", "settings")?;
// Workspace and its TempDir are cleaned up here
}
Ok(())
}TempDir in structs automatically propagates cleanup.
use std::fs;
use std::path::PathBuf;
// Manual cleanup - error prone
fn manual_approach() -> std::io::Result<()> {
let temp_path = std::env::temp_dir().join("manual_temp");
fs::create_dir(&temp_path)?;
// If code panics here, directory is never cleaned up!
// Potential resource leak
fs::remove_dir_all(&temp_path)?;
Ok(())
}
// RAII approach - automatic cleanup
fn raii_approach() -> std::io::Result<()> {
let dir = tempfile::tempdir()?;
// If code panics here, Drop is still called
// Directory is cleaned up during unwinding
Ok(())
}RAII ensures cleanup even in error paths; manual cleanup is fragile.
use tempfile::tempdir;
fn process_files() -> std::io::Result<()> {
let dir = tempdir()?;
// Multiple operations that might fail
let file1 = dir.path().join("file1.txt");
std::fs::write(&file1, "content1")?;
// If this panic occurs, dir still gets cleaned up
panic!("Simulated error");
let file2 = dir.path().join("file2.txt");
std::fs::write(&file2, "content2")?;
Ok(())
}
fn main() {
let result = std::panic::catch_unwind(|| {
let _ = process_files();
});
// Even after panic, temp directory is gone
// No orphaned files on disk
}TempDir prevents resource leaks during panics.
// Rust's guarantee for panic unwinding:
// 1. Panic begins
// 2. Stack unwinding starts
// 3. For each frame on the stack (in reverse order):
// - Run Drop for all local variables
// - Run Drop for function arguments owned by this frame
// 4. Panic propagates to caller
use tempfile::TempDir;
fn guaranteed_cleanup() {
let dir = TempDir::new().unwrap();
// Invariant: dir will have Drop::drop called
// This is guaranteed by Rust's semantics
// No matter how this function exits:
// - Normal return
// - Panic
// - Early return
// Drop will run
}Rust guarantees Drop runs during unwinding for panic = "unwind".
use tempfile::tempdir;
fn main() -> std::io::Result<()> {
// close() returns Result, allowing error handling
let dir = tempdir()?;
// Use directory...
// Explicit cleanup with error handling
dir.close()?; // Returns io::Result<()>
// vs implicit cleanup via Drop:
// let dir = tempdir()?;
// // ...
// Ok(()) // Drop runs, errors are logged, not returned
Ok(())
}close() returns Result<()>; implicit Drop logs errors.
use tempfile::tempdir;
use std::path::PathBuf;
// Sometimes you want conditional cleanup
fn conditional_cleanup(keep: bool) -> std::io::Result<PathBuf> {
let dir = tempdir()?;
// Do work...
std::fs::write(dir.path().join("output.txt"), b"data")?;
if keep {
// Prevent cleanup - caller owns directory now
Ok(dir.into_path())
} else {
// Normal cleanup
Ok(dir.path().to_path_buf())
// Drop runs at end of function, cleaning up
}
}
fn main() -> std::io::Result<()> {
// Let it clean up
let path1 = conditional_cleanup(false)?;
// Directory is cleaned up when conditional_cleanup returns
// Keep it around
let path2 = conditional_cleanup(true)?;
// Directory persists after function returns
Ok(())
}into_path() enables conditional persistence.
use tempfile::TempDir;
use std::sync::Arc;
use std::thread;
fn main() -> std::io::Result<()> {
// TempDir is not Clone or Copy
// It represents ownership of a resource
let dir = TempDir::new()?;
// To share across threads, use Arc and the path
let path = Arc::new(dir.path().to_path_buf());
let handles: Vec<_> = (0..4)
.map(|i| {
let path = Arc::clone(&path);
thread::spawn(move || {
std::fs::write(path.join(format!("file{}", i)), b"data").unwrap();
})
})
.collect();
for h in handles {
h.join().unwrap();
}
// dir cleans up when it goes out of scope
// (main thread holds ownership)
Ok(())
}TempDir is not thread-safe for sharing; pass paths instead.
| Cleanup Method | Timing | Error Handling | Panic Safe |
|----------------|--------|----------------|------------|
| Manual remove_dir_all | Explicit call | Can return Result | No |
| TempDir + Drop | End of scope | Logs error | Yes |
| TempDir::close() | Explicit call | Returns Result | Yes |
| TempDir::into_path() | Never | N/A | N/A |
// Drop doesn't run in these scenarios:
// 1. panic = "abort" in Cargo.toml
// Program terminates immediately without unwinding
// 2. std::process::exit
// Terminate immediately without cleanup
// 3. std::mem::forget
// Explicitly prevent Drop
// 4. Infinite loops without function calls
// while true {} // Never reaches Drop
// 5. Segfaults / abort from C code
// Undefined behavior
use tempfile::tempdir;
use std::mem::forget;
fn main() {
let dir = tempdir().unwrap();
// forget prevents Drop from running
forget(dir);
// Directory now leaks (will be cleaned up by OS eventually)
}Drop is not guaranteed in all scenariosājust unwinding panics.
tempfile::tempdir ensures cleanup through Rust's RAII pattern and the Drop trait:
The mechanism: TempDir is a guard type that owns a temporary directory path. When the guard goes out of scopeāwhether through normal execution or panic unwindingāRust calls Drop::drop, which removes the directory and its contents.
Panic safety: Rust's default panic behavior is to unwind the stack, calling destructors for all live values in reverse order of creation. TempDir::drop runs during this unwinding, ensuring cleanup even when errors occur.
Key operations:
tempdir() creates a directory and returns a guardDrop::drop removes the directory (automatic)close() explicitly cleans up and returns Result<()>into_path() consumes the guard without cleanupWhen cleanup fails: If Drop cannot remove the directory (permissions, files locked), the error is logged to stderr. Drop implementations should not panic, so errors are suppressed rather than propagated.
When cleanup might not run: panic = "abort" configuration, std::process::exit, std::mem::forget, or abnormal termination (segfaults, signals) bypass Drop entirely. In these cases, the temporary directory persists until the operating system cleans it up.
Best practice: Use TempDir for automatic cleanup in normal code paths. Use close() when you need to handle cleanup errors explicitly. Use into_path() when you need the directory to persist after the guard goes out of scope.