Rust Walking Directories
Walking a directory tree means visiting every file inside every subfolder. Searches, backups, and cleanup scripts need the whole tree.
List what is inside a folder
read_dir returns entries. Sort them for a stable print order.
let mut paths: Vec<_> = fs::read_dir("shop").unwrap().map(|e| e.unwrap().path()).collect();
paths.sort();
for path in &paths {
println!("{}", path.display());
}Files and folders are different kinds
file_type knows which one each entry is.
let mut entries: Vec<_> = fs::read_dir("shop").unwrap().map(|e| e.unwrap()).collect();
entries.sort_by_key(|e| e.file_name());
for entry in &entries {
let ft = entry.file_type().unwrap();
println!("{}: dir = {}", entry.file_name().to_string_lossy(), ft.is_dir());
}Walk deeper with recursion
The same function calls itself on every folder it finds.
fn print_tree(path: &Path, depth: usize) {
let mut entries: Vec<_> = fs::read_dir(path).unwrap().map(|e| e.unwrap().path()).collect();
entries.sort();
for entry in entries {
println!("{}{}", " ".repeat(depth), entry.display());
if entry.is_dir() {
print_tree(&entry, depth + 1);
}
}
}Add up file sizes
len() on metadata gives bytes. Only files have them.
fn total_size(path: &Path) -> u64 {
let mut total = 0;
for entry in fs::read_dir(path).unwrap() {
let entry = entry.unwrap();
if entry.file_type().unwrap().is_dir() {
total += total_size(&entry.path());
} else {
total += entry.metadata().unwrap().len();
}
}
total
}
println!("total = {} bytes", total_size(Path::new("shop")));Example
use std::fs;
use std::path::Path;
fn collect_paths(path: &Path, out: &mut Vec<std::path::PathBuf>) {
let mut entries: Vec<_> = fs::read_dir(path)
.unwrap()
.map(|e| e.unwrap().path())
.collect();
entries.sort();
for entry in entries {
out.push(entry.clone());
if entry.is_dir() {
collect_paths(&entry, out);
}
}
}
fn main() {
let root = Path::new("walk_demo");
let _ = fs::remove_dir_all(root);
fs::create_dir_all(root.join("sub")).unwrap();
fs::write(root.join("a.txt"), "hello").unwrap();
fs::write(root.join("sub/b.txt"), "rust!").unwrap();
let mut paths = vec![root.to_path_buf()];
collect_paths(root, &mut paths);
let mut total = 0u64;
for path in &paths {
if path.is_file() {
let size = fs::metadata(path).unwrap().len();
total += size;
println!("{} ({} bytes)", path.display(), size);
} else {
println!("{}/", path.display());
}
}
println!("total = {} bytes", total);
fs::remove_dir_all(root).unwrap();
}
Key points
- Use fs::read_dir(path) to list one folder.
- It returns an iterator of Result<DirEntry>.
- A folder can change while you read it.
- Use entry.file_type() to learn what an entry is: is_dir(), is_file() and is_symlink() tell you which kind you are looking at.
- Walk by recursion: a small function lists a folder, calls itself on each subfolder, and the depth grows by itself.
- Use entry.metadata() (or fs::metadata(path)) when you need a file's size: len() counts bytes, no reading required.
- Walking a directory tree visits every file in every subfolder, not just one folder.
The closing program builds a tiny two-level folder tree. It walks it in sorted order, printing folders and file sizes. It totals the bytes. Then it removes the tree it made.
Note
read_dir promises no order.
Entries come back however the disk feels like.
Collect and sort when your output must be stable.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why does the closing program sort the paths before printing them?