---
title: How to create temporary files
summary: Create temporary files in Rust using std::env::temp_dir() and std::fs::File::create(), then manually delete them when finished.
lang: en
slug: how-to-create-temporary-files
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-create-temporary-files/
---

# How to create temporary files



## When you need a scratchpad that disappears

You're building a tool that converts images. The conversion library requires a file path to work, but you don't want to leave a 500-megabyte intermediate file cluttering the user's home directory. Or you're writing a config generator that assembles a JSON file before moving it into place. You need storage that exists for a moment, does its job, and then vanishes without a trace.

That's a temporary file. Rust's standard library gives you the path to the system's temporary directory. It does not manage the file's lifecycle. You create the file, you write to it, you delete it. If your program crashes, the file stays. The operating system might clean it up on reboot, but that behavior varies by platform and is not a guarantee. For anything beyond a quick script, the ecosystem relies on the `tempfile` crate to handle uniqueness and automatic cleanup.

## How temporary files work in Rust

The standard library provides `std::env::temp_dir()` to locate the temporary directory. This function returns a `PathBuf` pointing to the OS-specific scratchpad. On Linux, this is usually `/tmp`. On Windows, it points to the user's temp folder under `%TEMP%`. On macOS, it's `/var/folders` with a unique hash.

Calling `temp_dir()` does not create the directory. It assumes the directory exists. If the directory is missing, file creation will fail. The function also does not generate unique filenames. If you append a fixed name like `temp.txt`, you get a predictable path. Predictable paths in shared directories are dangerous. Two processes running at the same time will collide, overwriting each other's data.

The standard library approach requires manual lifecycle management. You construct the path, create the file, write data, and call `std::fs::remove_file` to delete it. If any step panics or returns an error before the deletion call, the file leaks. The file remains on disk until the user or the OS cleans it up.

```rust
use std::env;
use std::fs::File;
use std::io::Write;

fn main() {
    // Get the OS-specific temporary directory path.
    let temp_dir = env::temp_dir();
    
    // Construct a path inside the temp directory.
    // WARNING: This name is predictable and not unique.
    let path = temp_dir.join("scratch.txt");
    
    // Create the file, truncating it if it already exists.
    let mut file = File::create(&path).expect("Failed to create temp file");
    
    // Write data to the file.
    file.write_all(b"Temporary data").expect("Failed to write");
    
    // Manually remove the file to prevent clutter.
    // If the program panics before this line, the file leaks.
    std::fs::remove_file(&path).expect("Failed to delete temp file");
}
```

Don't hardcode filenames in the temp directory. Predictable names are race conditions waiting to happen.

## Walking through the lifecycle

When you call `env::temp_dir()`, Rust queries the environment variables or system APIs to find the temp directory. The result is a `PathBuf`, an owned path that you can modify. Calling `join` creates a new `PathBuf` with your filename appended. The `join` method handles path separators correctly for the platform, so you don't need to worry about slashes or backslashes.

`File::create` opens the file for writing. If the file exists, it is truncated to zero length. If the file does not exist, it is created. The function returns a `File` handle wrapped in a `Result`. You must handle the error, or the program panics. Once you have the file, you can write bytes to it using `write_all` or other I/O methods.

After writing, you call `remove_file` to delete the file. This function takes a path reference. It removes the file from the filesystem. If the file is still open by another process, or if permissions prevent deletion, the function returns an error.

The critical weakness here is the gap between creation and deletion. If `write_all` panics, execution jumps to the panic handler. The `remove_file` call never runs. The file remains on disk. In a long-running service, these leaked files can fill the disk. In a security-sensitive context, leaked temp files might expose sensitive data.

## The race condition trap

Using a fixed filename in the temp directory introduces a time-of-check-time-of-use (TOCTOU) vulnerability. Even if you check whether the file exists before creating it, another process can create the file in the nanosecond between your check and your creation. Or two processes can run simultaneously and both create the file, with the second one overwriting the first.

This is not a theoretical risk. Attackers can exploit predictable temp file names to hijack processes. They create a symlink at the expected path pointing to a sensitive file like `/etc/passwd`. When your program opens the temp file for writing, it actually writes to the sensitive file. Or they create the file first and read your data as you write it.

The standard library does not protect against this. You must use a method that generates unique names atomically. The `tempfile` crate solves this by generating random filenames and creating the file in a single atomic operation. The OS ensures that no two processes get the same name.

## Automatic cleanup with tempfile

The `tempfile` crate is the community standard for temporary files. It provides types that manage the file's lifecycle automatically. When the type goes out of scope, the file is deleted. This works even if the program panics. The cleanup happens in the `Drop` implementation, which runs during stack unwinding.

`NamedTempFile` is the most common type. It creates a file with a unique name in the temp directory. You can access the path using the `path()` method. You can read and write using the file handle. When the `NamedTempFile` is dropped, the file is deleted.

```rust
use std::io::Write;
use tempfile::NamedTempFile;

/// Writes data to a temporary file and prints the path.
/// The file is automatically deleted when the function returns.
fn process_temp_data() -> std::io::Result<()> {
    // Create a temporary file with a unique name.
    // The file is created immediately and will be deleted on drop.
    let mut temp_file = NamedTempFile::new()?;
    
    // Write data to the file.
    temp_file.write_all(b"Secure temporary data")?;
    
    // Access the path if needed.
    println!("Temp file is at: {:?}", temp_file.path());
    
    // The file is deleted automatically when temp_file goes out of scope.
    Ok(())
}
```

Trust `NamedTempFile` to clean up. The file vanishes when the struct drops, panic or not.

There is a subtle gotcha with `NamedTempFile`. The file is tied to the struct, not the path. If you call `path()` and store the path in a variable, then drop the `NamedTempFile`, the file is deleted immediately. The path still exists in your variable, but the file on disk is gone. Any attempt to open that path will fail. Keep the `NamedTempFile` alive as long as you need the file.

If you want to keep the file after you're done, call the `persist()` method. This moves the file to a new location and returns a `Persisted` handle. The file is no longer temporary. You must manage its cleanup manually.

```rust
use std::fs::File;
use tempfile::NamedTempFile;

/// Creates a temp file and persists it to a final location.
fn create_config() -> std::io::Result<File> {
    let mut temp = NamedTempFile::new()?;
    temp.write_all(b"config=true")?;
    
    // Persist the file to a specific path.
    // The file is renamed and no longer auto-deleted.
    let final_path = std::path::Path::new("config.json");
    let persisted = temp.persist(final_path)?;
    
    Ok(persisted)
}
```

## Unnamed temporary files

Sometimes you don't need a filename. You just need a file handle to read or write data. The `tempfile::tempfile()` function creates an unnamed temporary file. The file exists on disk, but the name is hidden. You cannot access the file by path. You can only use the file handle.

Unnamed temp files are useful for pipes or when you want to ensure no other process can find the file by name. They are also slightly faster because the system doesn't need to generate a unique name string.

```rust
use std::io::Write;
use tempfile::tempfile;

/// Writes to an unnamed temporary file.
fn use_unnamed_temp() -> std::io::Result<()> {
    // Create an unnamed temporary file.
    // No path is available. Only the handle exists.
    let mut file = tempfile()?;
    
    file.write_all(b"Hidden data")?;
    
    // The file is deleted when file is dropped.
    Ok(())
}
```

Keep the handle alive. If you drop the `TempFile`, the data disappears instantly.

## Pitfalls and compiler errors

Temporary files introduce several pitfalls. The most common is leaking files due to panics. If you use the standard library and forget to handle errors, a panic skips cleanup. The `tempfile` crate prevents this by using `Drop`.

Another pitfall is assuming the temp directory exists. `std::env::temp_dir()` returns a path, but it does not create the directory. If the directory is missing, file creation fails. The `tempfile` crate also assumes the directory exists. It does not create it.

Race conditions occur when you use predictable names. Always use `tempfile` for unique names. If you must use the standard library, generate a unique name using a UUID crate or random bytes, and use atomic creation flags if available.

Compiler errors can appear when you mishandle paths. If you try to pass a `PathBuf` where a `&Path` is expected, the compiler might reject you. `PathBuf` implements `AsRef<Path>`, so most functions accept it directly. However, if you move a `PathBuf` into a closure and try to use it later, the compiler rejects you with `E0382` (use of moved value). Borrow the path instead of moving it.

```rust
use std::path::PathBuf;

fn example_move_error() {
    let path = PathBuf::from("/tmp/test");
    
    // This moves path into the closure.
    let _closure = || {
        println!("{:?}", path);
    };
    
    // ERROR: E0382 use of moved value `path`
    // println!("{:?}", path);
}
```

If you move the path, you can't use it again. Borrow the path with `&path` to avoid the move.

## Convention asides

The community convention is to use the `tempfile` crate for all temporary file operations. It is lightweight, well-tested, and handles edge cases across platforms. Adding `tempfile` to your project is standard practice. Run `cargo add tempfile` to include it.

When using `NamedTempFile`, the convention is to call `Rc::clone` style explicit methods when needed, but `tempfile` methods are clear enough. The `persist()` method is the standard way to convert a temp file to a permanent file. The `keep()` method is deprecated; use `persist()` instead.

File permissions are another convention. `tempfile` creates files with restricted permissions, usually `0o600` on Unix. This prevents other users from reading or writing the file. This is a security best practice. If you need different permissions, create the file with `tempfile` and then change permissions using `std::fs::set_permissions`.

## Decision matrix

Use `std::env::temp_dir()` when you need the directory path for a tool that manages its own lifecycle, or when you are implementing a custom storage backend that requires direct path access.

Use `tempfile::NamedTempFile` when you need a unique filename and want the file to delete automatically when the variable goes out of scope.

Use `tempfile::tempfile()` when you don't care about the filename and only need a file handle for reading or writing.

Use `std::fs::remove_file` when you are managing cleanup manually and have confirmed the file exists and is not in use.

Use `NamedTempFile::persist()` when you need to keep the temporary file at a specific location after processing is complete.

## Where to go next

- [How to Handle Symlinks in Rust](/en/how-to-handle-symlinks-in-rust/)
- [How to Use the dirs Crate for Platform-Specific Directories](/en/how-to-use-the-dirs-crate-for-platform-specific-directories/)
- [How to write to a file](/en/how-to-write-to-a-file/)