File System Operations

Removing Files and Directories

std::fs provides three distinct removal functions so a program can never accidentally delete a directory tree when it only intended to remove a single file or an empty directory: fs::remove_file, fs::remove_dir, and fs::remove_dir_all.

Comparison of Filesystem Removal Functions

Function Target Type If Target Is a Non-Empty Directory Symbolic Link Behavior
fs::remove_file(path) Regular file or symlink Fails (IsADirectory / PermissionDenied) Removes the symlink itself (never deletes the target of the symlink!)
fs::remove_dir(path) Empty directory Fails with ErrorKind::DirectoryNotEmpty! Fails on file symlinks (or removes directory junction on Windows)
fs::remove_dir_all(path) Directory and all its contents recursively (rm -rf) Deletes all files, symlinks, and subdirectories inside path, then removes path Removes child symlinks without following them into external target directories
Safety Boundary Between Removal Functions:

  `fs::remove_file(p)`     ---> Only unlinks a single file or symlink
  `fs::remove_dir(p)`      ---> Only removes a directory if it contains 0 entries (`rmdir`)
  `fs::remove_dir_all(p)`  ---> Recursively deletes everything inside `p` (`rm -rf`)
use std::fs;
use std::io::{self, ErrorKind};

fn main() -> io::Result<()> {
    let root = std::env::temp_dir().join("rust_ch50_rm_demo");
    let _ = fs::remove_dir_all(&root);
    fs::create_dir_all(&root)?;

    let file_path = root.join("item.txt");
    fs::write(&file_path, "temporary item")?;

    // 1. `remove_dir` refuses to delete a non-empty directory (`DirectoryNotEmpty`):
    match fs::remove_dir(&root) {
        Ok(()) => unreachable!(),
        Err(e) if e.kind() == ErrorKind::DirectoryNotEmpty => {
            println!("remove_dir refused non-empty directory (DirectoryNotEmpty)");
        }
        Err(e) => return Err(e),
    }

    // 2. Remove the file with `remove_file`:
    fs::remove_file(&file_path)?;
    println!("item.txt exists after remove_file: {}", fs::exists(&file_path)?);

    // 3. Now that `root` is empty, `remove_dir` succeeds:
    fs::remove_dir(&root)?;
    println!("root exists after remove_dir:      {}", fs::exists(&root)?);

    Ok(())
}
remove_dir refused non-empty directory (DirectoryNotEmpty)
item.txt exists after remove_file: false
root exists after remove_dir:      false

Idempotent Cleanup Helper (Ignoring ErrorKind::NotFound)

When cleaning up scratch files or cache directories that may or may not exist, match on ErrorKind::NotFound and map it to Ok(()) so genuine permission or I/O errors are still surfaced:

use std::fs;
use std::io::{self, ErrorKind};
use std::path::Path;

pub fn remove_file_if_exists(path: &Path) -> io::Result<()> {
    match fs::remove_file(path) {
        Ok(()) => Ok(()),
        Err(e) if e.kind() == ErrorKind::NotFound => Ok(()),
        Err(e) => Err(e),
    }
}

fn main() -> io::Result<()> {
    let missing = std::env::temp_dir().join("rust_ch50_already_gone.tmp");
    remove_file_if_exists(&missing)?;
    println!("idempotent remove_file_if_exists succeeded");
    Ok(())
}
idempotent remove_file_if_exists succeeded