Introduction: why might you need a file list in Rust?
\n\nImagine: you are writing a backup utility, a log analyzer, or a simple file manager in Rust. And then a basic but critical task arises — to get a list of all files in a specified directory. It seems simple, right? But in Rust, with its strict typing and ownership system, even such a simple operation hides several pitfalls.
\n\nThe question «How can I list files of a directory in Rust?» is one of the most popular on Stack Overflow (thousands of views!). And this is no coincidence: beginners often stumble over std::fs::read_dir, get confused with iterators, and forget about error handling. In this tutorial, I will show you two working approaches: simple (only files in one folder) and advanced (recursive traversal of all subdirectories). Let's go!
1. Preparation: create a new project
\n\nBefore writing code, let's create a new binary project. Open your terminal and run:
\n\ncargo new list_files_rs\ncd list_files_rs\n\nNow open src/main.rs — this is where we will write all the code. To start, we won't need any external dependencies, only the Rust standard library — std.
2. The simplest way: std::fs::read_dir
\n\nThe std::fs::read_dir function returns an iterator over the entries in a directory. Each entry is a DirEntry, which contains the name, path, type, and metadata of the file. Let's write a basic example:
use std::fs;\nuse std::path::Path;\n\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n let path = Path::new("."); // current directory\n\n // Read the contents of the folder\n let entries = fs::read_dir(path)?;\n\n for entry in entries {\n let entry = entry?; // unpack the Result\n let path = entry.path();\n\n // Check that it is a file (not a directory)\n if path.is_file() {\n println!("{}", path.display());\n }\n }\n\n Ok(())\n}\n\nWhat is happening here?
\n- \n
fs::read_dir(path)— opens the directory and returns aReadDir(iterator). \n - We iterate over each entry. Each call to
next()returns aResult<DirEntry>— hence the need forentry?. \n entry.path()returns the full path to the file/folder. \n - The
path.is_file()method filters out subdirectories. \n
Run the code with the command cargo run — you will see a list of files in the project root (e.g., Cargo.toml, src/main.rs).
2.1. Filtering by extension
\n\nOften you need to get only specific files, for example, only .rs or .txt. Let's add a filter:
use std::fs;\nuse std::path::Path;\n\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n let path = Path::new(".");\n\n for entry in fs::read_dir(path)? {\n let entry = entry?;\n let path = entry.path();\n\n // Check that it is a file and the extension is .rs\n if path.is_file() && path.extension().map_or(false, |ext| ext == "rs") {\n println!("{}", path.display());\n }\n }\n\n Ok(())\n}\n\nThe trick with map_or is needed to safely handle the case when a file has no extension — path.extension() returns Option<&OsStr>.
3. Recursive traversal: looking into all subfolders
\n\nA simple read_dir does not go into subdirectories. If you need to collect all files recursively (e.g., for finding duplicates), you'll have to write your own recursive function. Here is an elegant solution:
use std::fs;\nuse std::path::Path;\n\nfn visit_dirs(dir: &Path, depth: usize) -> Result<(), Box&l