fast as f*#! filename search.
fafind (and the shorter alias faf) is a zero-allocation, parallel filesystem search tool written in Rust, focused purely on filename matching.
It’s built to rip through millions of files with minimal overhead.
why this exists
Most search tools either:
- scan file contents (slow for this use case),
- allocate constantly, or
- bottleneck on output or synchronization
faf avoids all of that.
This is a hot-path optimized walker with:
- zero allocations per entry
- SIMD substring matching
- ASCII fast paths with Unicode fallback
- parallel traversal using a work-stealing scheduler
install
from source
git clone https://github.com/rywils/fafind
cd fafind
cargo build --release
sudo cp target/release/faf target/release/fafind /usr/local/bin/
Both faf and fafind are built from the same codebase. Use whichever name you prefer.
packages
- Arch (AUR):
fafind-bin— seepackaging/aur/README.md - Homebrew: see
RELEASE.md
The AUR package installs fafind and a faf symlink. From source, copy or link both names as you prefer.
usage
faf <target> [root]
# same as:
fafind <target> [root]
If root is not provided, it defaults to /.
matching modes
default (stem match)
Matches filename without extension.
faf main .
Matches:
main.rsmain.go
Does NOT match:
domain.rs
substring match (-s)
faf -s foo .
Matches:
foobar.txtmyfoo.rsprefoo
exact match (-p)
faf -p Makefile .
Matches:
Makefile
Only exact filename match (including extension).
terminal colors
When stdout is a terminal (and not -0 / --null), matches are highlighted:
| Color | Applies to |
|---|---|
| Dim | Path before the filename (/path/to/) |
| Green | The matched part of the name (stem in default/-p; each hit in -s) |
| Bold green | Stem in -p (exact) mode |
| Yellow | Extension (.rs, .js, .docx, …) |
| Orange | Non-matching parts of the name in -s only |
Stem match — faf main . on /app/main.rs: dim /app/, green main, yellow .rs
Substring — faf -s main . on has_dot_entry_main_corner.js:
- Orange:
has_dot_entry_and_corner - Green:
main - Yellow:
.js
Control coloring with --color auto (default), --color always, or --color never. Colors are off when output is piped to a file or tool unless you force --color always.
flags
case insensitive (-i / --ignore-case)
faf -i readme .
limit depth
faf --max-depth 3 main .
exclude directories
faf --exclude target,node_modules main .
respect .gitignore
faf --gitignore main .
filter by type
faf --type f main . # files only
faf --type d src . # directories only
faf --type a main . # any (default)
null-separated output (-0 / --null)
faf -0 main . | xargs -0 rm
Disables color highlighting.
verbose mode
faf -v main .
Sends to stderr:
[SCAN]for every visited entry[SKIP]for excluded directories[ERROR]for unreadable entries
Matches are still written to stdout as normal.
quiet mode (-q / --quiet)
Suppresses the summary line printed to stderr after the search completes.
faf -q main .
example
faf -i --exclude target,node_modules --max-depth 5 main .
performance characteristics
zero allocation hot path
- no heap usage per file
- stack buffers for ASCII matching
- fallback only when necessary
parallel by default
- uses all available CPU cores
- work-stealing via
ignore::WalkBuilder
efficient output
- each worker formats matches into a private buffer
- TTY: batched stdout writes (64 KiB) to avoid locking on every match
- pipes: flush after each match line so scripts see results immediately
- atomic counters for scan/match totals (no mutex on the hot path)
ASCII fast path
- ~95% of filenames handled without Unicode overhead
SIMD substring search
- powered by
memchr::memmem - length prefilter skips obvious non-matches before full comparison
implementation details
clapfor CLI parsingignorefor parallel walkingmemchrfor fast substring searchsmallvecfor stack-allocated exclude lists
key design decisions
- no channels
- no shared match queues
- no UTF-8 conversion on Unix (raw bytes)
- matcher length prefilter and per-worker config caching
output format
- newline-separated by default
- NUL-separated with
-0 - raw OS bytes on Unix (no encoding overhead)
exit codes
0 = matches found
1 = no matches
2 = error / invalid usage
what this is NOT
- not a content search tool (use
greporrg) - not a fuzzy matcher
- not a UI tool
This is a fast, deterministic filename matcher.
changelog
See CHANGELOG.md.
license
MIT