walkdir prunes the tree as it descends instead of filtering a finished list
Rust library for walking directories recursively.
At a glance
- What is it?
- walkdir is a Rust directory iterator with one runtime dependency, dual-licensed MIT and Unlicense, whose filter_entry adapter skips whole subtrees at the point of descent. Following symlinks can loop forever, and the README does not offer a depth limit as the fix.
- Who is it for?
- walkdir is the right choice for a Rust program that needs to traverse a directory tree with more control than std's read_dir gives, particularly when you need pruning or a bound on open descriptors. Do not reach for it in a non-Rust project, since the whole ecosystem here is Rust-specific, and check the API documentation for cycle handling before enabling `follow_links(true)`.
- Can I use it commercially?
- Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 122 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One dependency, and a public domain fallback
Start with the manifest, because it settles most of the design questions.
The package is `walkdir` at version 2.5.0, edition 2018, categorised under filesystem, with a single dependency: `same-file` at 1.0.1. There is one target-specific addition, `winapi-util` 0.1.1 for `cfg(windows)`. A dev-dependency on `doc-comment` 0.3 exists for the documentation tests.
The manifest's only runtime dependency is `same-file` at 1.0.1.
The licence is unusual and worth noting for commercial use. The manifest declares `Unlicense/MIT`, the repository carries both `UNLICENSE` and `LICENSE-MIT` files plus a `COPYING` file, and the README says the crate is dual-licensed under MIT or the UNLICENSE. The Unlicense is a public domain dedication. Either option works for a closed-source product: MIT keeps the notice, Unlicense asks for nothing. This is a rare licence pairing in Rust, and it is about as permissive as a crate gets.
The repository is a workspace with `walkdir-list` as a second member, so the companion binary lives in the same tree rather than in a separate repository.
Adding it and iterating, with error handling you choose
The dependency is one entry:
[dependencies]
walkdir = "2"The basic iteration is a for loop over `WalkDir`, and each item is a `Result` you unwrap:
use walkdir::WalkDir;
for entry in WalkDir::new("foo") {
let entry = entry.unwrap();
println!("{}", entry.path().display());
}The second version changes behaviour in a way that matters operationally. The README describes it as iterating over all entries and ignoring errors that may arise, and the parenthetical example is that it silently skips directories the owner of the running process does not have permission to access.
use walkdir::WalkDir;
for entry in WalkDir::new("foo").into_iter().filter_map(|e| e.ok()) {
println!("{}", entry.path().display());
}That is a real choice with a real consequence. The `filter_map(|e| e.ok())` version will not fail, but a partial traversal looks identical to a complete one. A backup tool or an indexer built on it will quietly skip everything under a directory it cannot read, and the symptom appears later as missing files rather than as an error. If you care about coverage, log the errors instead of discarding them.
filter_entry skips subtrees, and following links needs a guard
The pruning mechanism is the `filter_entry` iterator adapter, and the README's example is titled skip hidden files and directories efficiently on unix.
use walkdir::{DirEntry, WalkDir};
fn is_hidden(entry: &DirEntry) -> bool {
entry.file_name()
.to_str()
.map(|s| s.starts_with("."))
.unwrap_or(false)
}
let walker = WalkDir::new("foo").into_iter();
for entry in walker.filter_entry(|e| !is_hidden(e)) {
let entry = entry.unwrap();
println!("{}", entry.path().display());
}The efficiency claim is structural, not incidental. Returning false from the predicate prevents walkdir from descending into that directory at all, so the cost of a hidden directory is one read rather than a full traversal. The same code with a filter applied after the fact would read everything and then discard it.
Note that the predicate receives a `DirEntry` rather than a path, and the example reads `file_name()` and converts with `to_str()`, defaulting to false on a conversion failure. That default is deliberate and correct for this predicate: a name that is not valid UTF-8 is not treated as hidden.
The other documented mode is following symbolic links, which is one method call on the same iterator:
use walkdir::WalkDir;
for entry in WalkDir::new("foo").follow_links(true) {
let entry = entry.unwrap();
println!("{}", entry.path().display());
}This is the option to be careful with. The README documents `follow_links(true)` but does not describe loop-detection behaviour or a maximum depth setting, so those details should be checked in the API documentation before following links over an untrusted tree.
The performance claim is against find and nftw, unbenchmarked
The README's performance section is short and unusually honest about its own evidence, so read the hedging rather than the headline.
The short version is that performance is comparable with `find` and glibc's `nftw` on both a warm and cold file cache. The author states he cannot observe any performance difference after running `find /`, `walkdir /` and `nftw /` on a local SSD file system with around three million entries, and that he is reasonably confident the crate makes as few system calls and close to as few allocations as possible.
Then the sentence that should shape your expectations: no benchmarks have been recorded.
What follows instead is a procedure you can run yourself, and the procedure is more useful than a table. Set `DIR` to the tree you care about, drop caches before each run for a cold measurement, warm the cache with `find`, then time each implementation:
DIR=$HOME
sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
find $DIR
time find $DIR
cargo build --release --example walkdir
time ./target/release/examples/walkdir $DIR
gcc -O3 -o nftw ./compare/nftw.c
time ./nftw $DIR
time python ./compare/walk.py $DIRThe `compare/` directory in the repository holds the `nftw.c` and `walk.py` baselines, so the comparison is reproducible rather than anecdotal. Note that the Python baseline is included with a deliberately dismissive comment in the README, and that the result depends entirely on your filesystem and cache state.
Version floor, MSRV policy and maintenance state
The minimum supported `rustc` version is 1.60.0, and the README documents the policy around it, which is the part most projects skip.
The policy is that the minimum Rust version can be increased in minor version updates. The example given: if `crate 1.0` requires Rust 1.20.0, then every `crate 1.0.z` will also require 1.20.0 or newer, while `crate 1.y` for `y` greater than zero may require a newer minimum. So a patch release will not raise your compiler requirement, but a minor release can.
The stated general attitude is conservatism with respect to the minimum supported version. For a dependency that most Rust projects pull in indirectly, that matters: walkdir tends to sit low in the tree, and a floor it raises becomes a floor your build inherits.
On versions and maintenance, the crate is at 2.5.0 on a 2.x line. The repository has no GitHub releases, so version history lives on crates.io rather than in tags on the site, and the manifest carries a `#:version` marker on the version line, which is a convention for scripted version bumps.
The repository is not archived and the last push was on 2026-06-23. For a crate at 2.5.0 that is an unremarkable cadence rather than an active development signal.
One legacy detail is visible in the manifest. A `[badges]` table still lists travis-ci and appveyor repositories, both of which are long defunct as build services, while the README badge now points at a GitHub Actions workflow. The stale table is harmless because badges are ignored by modern Cargo, but it dates the manifest.
Why not std, ignore, or a language you already use
Two comparisons matter, and both are about what walkdir adds over the obvious alternatives.
walkdir provides recursive iteration, support for following symbolic links, controls for open file descriptors, and pruning that avoids descending into excluded entries.
When choosing a traversal library, compare the desired filtering, ordering, concurrency and error-handling behaviour.
The important limitation is that walkdir is a Rust library; projects written in other languages need to use their own ecosystem's traversal options.
Documentation is on docs.rs, and the README links it as the place to read the API. There is no separate book or guide, which for a crate this small is the right amount.
Editorial conclusion
walkdir is the right choice for a Rust program that needs to traverse a directory tree with more control than std's read_dir gives, particularly when you need pruning or a bound on open descriptors. Do not reach for it in a non-Rust project, since the whole ecosystem here is Rust-specific, and check the API documentation for cycle handling before enabling `follow_links(true)`. Verify first that you handle per-entry errors deliberately, because the iterator yields Result and the example that ignores errors also silently skips directories your process cannot read.
Frequently asked questions
How do I add walkdir to a Rust project?
Add it to `Cargo.toml` under dependencies as `walkdir = "2"`. The crate is at version 2.5.0, edition 2018, and its minimum supported rustc version is 1.60.0.
How do I walk a directory and handle errors in walkdir?
The iterator yields a `Result` per entry. Unwrapping it panics on error, while `filter_map(|e| e.ok())` discards errors and silently skips directories the process cannot read. Logging the errors instead of discarding them is safer for any tool that must cover the whole tree.
How do I skip hidden directories efficiently with walkdir?
Use the `filter_entry` iterator adapter, which prevents descent rather than filtering results afterwards. The README's example tests `entry.file_name()` for a leading dot and calls it an efficient skip on unix.
Does walkdir follow symbolic links?
Yes, with `follow_links(true)` on the walker. The README does not describe cycle handling or a maximum depth setting, so check the API documentation before enabling it on an untrusted tree.
What license is walkdir released under?
Dual-licensed under MIT or the UNLICENSE. The repository carries LICENSE-MIT, UNLICENSE and COPYING files, and the manifest declares `Unlicense/MIT`.
How does walkdir compare to find and glibc nftw on speed?
The README states the performance is comparable on both warm and cold file cache, based on running find, walkdir and nftw over about three million entries on an SSD. No benchmarks are recorded, but the compare/ directory holds the nftw.c and walk.py baselines so you can reproduce the comparison yourself.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/burntsushi-walkdir)Was this useful?