This guide is for operators, packagers, integrators, and developers who need to understand
how fd works internally — how it is built, how it traverses the filesystem, how it
applies filters, how it executes commands, and how it can be extended or hardened. End-user
task documentation lives in the companion User Guide; this document covers everything
behind it.
fd is a single-binary command-line tool written in Rust (edition 2024, MSRV 1.90.0). It
has no daemon, no network surface, no persistent state, and no configuration file. Its
behavior is determined entirely by command-line arguments, ignore files on disk, and a
handful of environment variables. That simplicity is the dominant fact of its operations
story.
1. Executive Summary
| Aspect | Detail |
|---|---|
| What it is | A fast, parallel filesystem search tool — a friendlier alternative to find. |
| Language / edition | Rust, edition 2024, minimum supported Rust 1.90.0. |
| Crate / binary | Crate fd-find (crates.io); binary fd (fdfind on Debian/Ubuntu). |
| License | Dual-licensed MIT OR Apache-2.0. |
| Runtime model | One-shot CLI process. Parse args → build config → parallel walk → print or exec → exit. |
| Concurrency | Multi-threaded producer/consumer: parallel directory walker feeds a result receiver. |
| External processes | Only via --exec/--exec-batch, and ls for --list-details. |
| Persistent state | None. Reads ignore files; writes nothing except stdout/stderr. |
| Network | None. |
| Configuration | CLI flags + ignore files + a few environment variables. No config file. |
The codebase is small and cohesive: ~23 source files, ~182 KB of Rust under src/,
organized into a flat top-level module set plus three sub-modules (exec, filter,
fmt). The heavy lifting of directory traversal and gitignore semantics is delegated to
BurntSushi's ignore crate (the same engine that powers ripgrep); fd itself is, in
essence, a well-tuned configuration and filtering layer over that crate plus a parallel
output/exec pipeline.
2. Architecture
2.1 High-level data flow
┌──────────────────────────────────────────────┐
argv ── Opts::parse() ──▶│ main.rs : run() │
(clap derive, src/cli.rs)│ set_working_dir → search_paths │
│ build pattern regexes (smart case, glob) │
│ construct_config() ──▶ Config │
└───────────────────────┬──────────────────────┘
│ walk::scan(paths, regexps, config)
▼
┌──────────────────────────────────────────────┐
│ walk.rs : WorkerState │
│ │
│ build_walker() → ignore::WalkParallel │
│ │
│ spawn_senders() receive() │
│ ┌───────────────┐ ┌──────────────────┐ │
│ │ N walker │ │ receiver thread │ │
│ │ threads: │ tx │ ReceiverBuffer │ │
│ │ traverse + ├────▶│ buffer ≤100ms │ │
│ │ filter + │ rx │ then stream, │ │
│ │ batch results │ │ sorted if fast │ │
│ └───────────────┘ └────────┬─────────┘ │
│ crossbeam bounded channel │ │
└──────────────────────────────────┼───────────┘
▼
stdout (print_entry) OR
exec::job / exec::batch (--exec / -X)
▼
ExitCode.exit()
2.2 The two-phase pipeline
Phase 1 — Setup (main.rs). run() parses arguments into the Opts struct, optionally
generates shell completions and returns early, sets the working directory if -C was
given, resolves the search paths, and turns the raw pattern(s) into regex strings. Glob
patterns are translated to regex via globset; --exact/--fixed-strings are escaped and
optionally anchored. construct_config() then folds all of Opts into a single immutable
Config struct (src/config.rs) that the rest of the program reads. Finally the regex
strings are compiled into regex::bytes::Regex objects (byte regexes, so non-UTF-8
filenames still match) and walk::scan is called.
Phase 2 — Search (walk.rs). WorkerState owns the patterns, the config, and two
atomic flags (quit_flag, interrupt_flag). scan():
- Builds an
ignore::WalkParallelwalker from the config (build_walker). - Installs a Ctrl-C handler (when printing with colors) that sets the quit flag on first press and exits immediately on the second.
- Creates a bounded crossbeam channel sized
2 × threads. - In a
thread::scope, spawns the receiver, then runs the parallel walker's sender closures, then joins.
2.3 The producer side: parallel walking and filtering
spawn_senders() hands ignore::WalkParallel::run a closure that executes on every walker
thread for every entry. The closure is the heart of fd's filtering and applies checks in
a deliberate, cheap-to-expensive order so that most entries are rejected before any
stat()-style syscall:
- Quit check — bail out if the quit flag is set (Ctrl-C / broken pipe).
--ignore-contain— if a directory contains the named marker file, skip it (CACHEDIR.TAG-style pruning).- Root skip — depth-0 root entries are never reported.
- Broken symlinks —
NotFounderrors on a symlink are converted into a reportable broken-symlink entry rather than an error. --min-depth— drop entries above the minimum depth.- Name / full-path match — apply every compiled regex to the filename (or the absolute
path when
--full-pathis set). All patterns must match (--andsemantics). --extension— a case-insensitive regex set built from the requested extensions.--type— file-type filtering via theFileTypesbitmask (src/filetypes.rs).--owner(Unix) — uid/gid checks against the entry's metadata.--size— only for regular files; every size constraint must be satisfied.--changed-within/--changed-before— modification-time constraints.- Color precomputation — when printing with colors, the entry's style is computed on the walker thread (parallelizing the work).
- Send — the surviving entry is pushed into a batch and sent on the channel.
--prune— if set, returnWalkState::Skipso matching directories are not descended into.
Results are sent in batches (Batch/BatchSender) rather than one at a time, which
amortizes channel and lock overhead. The default batch limit is 0x100 (256); when running
--exec in non-batch mode with multiple threads the limit drops to 1 to spread work
evenly across the executor threads.
2.4 The consumer side: buffering, sorting, streaming
receive() chooses one of three behaviors based on the config:
--exec-batch— drain the whole channel and callexec::batchonce.--exec— spawn a pool ofthreadsexecutor threads, each runningexec::job, consuming results as they arrive.- Plain printing — run a
ReceiverBufferon a locked, buffered stdout.
The ReceiverBuffer implements fd's signature output behavior. It starts in
Buffering mode with a deadline of max_buffer_time (default 100 ms). If the search
finishes within that window, the buffered results are sorted and printed — so quick
searches give deterministic, alphabetized output. If the deadline passes or the buffer
exceeds MAX_BUFFER_LENGTH (1000 entries), it switches to Streaming mode and prints
results as they arrive (unsorted) for responsiveness on large searches. --max-results
short-circuits the loop; -q/--quiet returns HasResults(true) on the first match
without printing anything.
Note
This buffer-then-stream design is why a
fdsearch of a small tree appears sorted while a search of a huge tree streams in filesystem order. It is intentional, and tunable through the hidden--max-buffer-timeoption.
2.5 Command execution (src/exec)
--exec/--exec-batch strings are parsed into a CommandSet of CommandTemplates, each a
list of FormatTemplate tokens (src/fmt). Placeholders ({}, {/}, {//}, {.},
{/.}) are substituted per result. Key behaviors:
- If no placeholder appears in a template, an implicit
{}is appended. - One-by-one mode (
exec::job) generates and runs one command per result; output is buffered per-command when running multi-threaded so concurrent commands don't interleave, and streamed directly when single-threaded. - Batch mode (
exec::batch) builds command lines incrementally withCommandBuilder, using theargmaxcrate to respect the OS maximum command-line length — it flushes and re-runs the command when the next argument wouldn't fit, or when--batch-sizeis reached. Batch templates are validated to contain at most one placeholder and a fixed executable as the first argument. - Exit codes from all executed commands are merged: any non-zero result makes
fdexit1.
2.6 Module reference
| Module | Responsibility |
|---|---|
src/main.rs |
Entry point, run() orchestration, config construction, regex building, smart-case/glob/exact logic, the embedded default LS_COLORS, and the --list-details → ls resolution. |
src/cli.rs |
The Opts clap-derive struct (every flag), the FileType/ColorWhen/StripCwdWhen/HyperlinkWhen enums, and the hand-rolled Exec clap Args impl for --exec/--exec-batch. |
src/config.rs |
The immutable Config struct consumed by the walker and output stages. |
src/walk.rs |
The parallel walker, WorkerState, batching channel, ReceiverBuffer, sender closure (all filtering), Ctrl-C handling, and scan(). |
src/dir_entry.rs |
DirEntry wrapper over ignore::DirEntry plus broken-symlink entries; lazy metadata/style caching; ordering for sorted output. |
src/filetypes.rs |
FileTypes bitmask and should_ignore() type-filter logic. |
src/filter/size.rs |
SizeFilter parsing (<+-><NUM><UNIT>) and matching. |
src/filter/time.rs |
TimeFilter parsing (durations, datetimes, @timestamp) via jiff. |
src/filter/owner.rs |
OwnerFilter (Unix) uid/gid parsing with negation. |
src/fmt/mod.rs |
FormatTemplate/Token parsing and generation (placeholders, brace escaping, path-separator substitution). |
src/fmt/input.rs |
basename / dirname / remove_extension path helpers. |
src/exec/mod.rs |
CommandSet/CommandTemplate/CommandBuilder; batch length handling. |
src/exec/command.rs |
Spawning commands, output buffering, error handling. |
src/exec/job.rs |
The per-result (job) and whole-batch (batch) execution loops. |
src/output.rs |
print_entry and its colorized / uncolorized / format variants; trailing-slash and hyperlink emission. |
src/hyperlink.rs |
file:// URL construction with percent-encoding for terminal hyperlinks. |
src/regex_helper.rs |
Smart-case detection and leading-dot detection by parsing patterns to a regex HIR. |
src/filesystem.rs |
Path normalization, type predicates, emptiness check, default path separator (MSYS handling). |
src/exit_codes.rs |
The ExitCode enum, conversion to process codes, and SIGINT re-raise. |
src/error.rs |
print_error — the single [fd error]: stderr formatter. |
3. Requirements
Build requirements
| Requirement | Value |
|---|---|
| Rust toolchain | 1.90.0 or newer (MSRV), edition 2024. |
| Build command | cargo build --release. |
| Default features | use-jemalloc, completions. |
| Optional allocator | tikv-jemallocator (jemalloc) on most non-Windows, non-macOS targets. |
Cargo features
| Feature | Default? | Effect |
|---|---|---|
use-jemalloc |
Yes | Use jemalloc as the global allocator for better multithreaded performance. |
completions |
Yes | Enable --gen-completions (pulls in clap_complete). |
base |
— | Convenience set: use-jemalloc only (no completions). |
default |
— | use-jemalloc + completions. |
Note
jemalloc is deliberately disabled on macOS due to a known jemalloc/Catalina interaction (see the comment referencing sharkdp/fd#498 in
Cargo.tomland the matchingcfgon the global allocator inmain.rs). It is also disabled on Windows, Android, the BSDs, illumos, 32-bit musl, and riscv64. These two locations must be kept in sync.
Runtime requirements
- No runtime dependencies for normal searching.
--list-details/-lrequireslsonPATH(GNUlspreferred; required on Windows).--owneris Unix-only (compiled out elsewhere).
Release build profile
The release profile is tuned for a small, fast binary:
[profile.release]
lto = true # link-time optimization
strip = true # strip symbols
codegen-units = 1 # maximize optimization (slower compile)
4. Configuration Mastery
fd has no configuration file. Its full configuration surface is: command-line flags
(see the User Guide §5 for the complete list), ignore files, and environment variables.
4.1 Ignore-file precedence
fd layers several ignore sources, wired up in WorkerState::build_walker:
| Source | Toggle | Notes |
|---|---|---|
| Hidden-file filtering | --hidden disables |
ignore crate .hidden(). |
.gitignore, git global, .git/info/exclude |
--no-ignore-vcs / --no-ignore |
Only applied inside a git repo unless --no-require-git. |
.ignore / .fdignore |
--no-ignore |
.fdignore registered via add_custom_ignore_filename. |
| Parent-directory ignore files | --no-ignore-parent |
.parents(), gated on VCS/fd ignore being on. |
| Global ignore file | --no-global-ignore-file / --no-ignore |
Path: <config-dir>/fd/ignore (e.g. ~/.config/fd/ignore), located via etcetera. |
| Custom ignore files | --ignore-file <path> |
Lowest precedence; .gitignore syntax. |
--exclude <glob> |
— | Highest precedence override, built as ignore overrides (!pattern). |
Important
--exclude/-Eis implemented as anignoreoverride and beats all other ignore logic. Use it to force-skip a path regardless of what any ignore file says.
4.2 Environment variables
| Variable | Used by | Effect |
|---|---|---|
LS_COLORS |
Output coloring | Parsed by lscolors; defines per-type/per-extension colors. If unset, fd falls back to a built-in molokai-style default embedded in main.rs. |
NO_COLOR |
Color decision | If set non-empty, disables colorized output in auto mode. |
TERM |
Windows color | On Windows, presence helps decide ANSI color support. |
MSYSTEM |
Path separator | On Windows under MSYS/MSYS2, makes fd default to / as the path separator. |
SHELL |
--gen-completions |
Used to auto-detect the shell when no shell name is given. |
XDG_CONFIG_HOME / platform config dir |
Global ignore | Determines where fd/ignore is read from (via etcetera). |
4.3 Color and hyperlink decision logic
Colorized output is enabled only when all of these hold (in auto mode): ANSI color is
supported (always true on Unix; on Windows requires successful ANSI enablement or TERM),
NO_COLOR is not set, and stdout is an interactive terminal. --color always forces it on;
--color never forces it off. Hyperlinks (--hyperlink) default to never; in auto mode
they are emitted only when color is enabled.
4.4 Path-output options
--path-separatoroverrides the separator in printed paths (and is validated to be a single byte on Windows).--strip-cwd-prefixcontrols whether the leading./is stripped. The default (auto) strips it unless--print0or an exec command is in play, where a leading./protects paths that begin with-from being mistaken for options.
5. Security Analysis
fd is a read-oriented local tool with a small, well-contained attack surface. The notable
considerations for operators:
5.1 Command execution is the primary risk surface
--exec/--exec-batch run arbitrary programs with matched paths as arguments. This is by
design and is the only way fd spawns processes (aside from ls for --list-details).
- Arguments are passed directly to the executable via
argmax::Command(a thin wrapper overstd::process::Command);fddoes not invoke a shell, so there is no shell interpolation or word-splitting of the matched paths. This avoids the classicfind -execshell-injection class of problems. - Because no shell is used, shell built-ins (
cd,export) are not runnable directly — this is a frequent source of "Command not found" reports (issue #1944), and is also a safety property.
Caution
If you build
fd -x/-Xcommand lines from untrusted input (e.g. a user-supplied directory or pattern), the matched paths are safe (no shell), but never interpolate untrusted text into the command template itself. Treat the command template as code.
5.2 Symlink and filesystem boundaries
- Symlinked directories are not followed by default;
--followopts in. This prevents accidental traversal into linked trees or symlink loops. --one-file-system(--mount/--xdev) keeps the walk within a single filesystem, useful when scanning a mount point without descending into network or pseudo filesystems.- Broken symlinks are reported as entries, not silently dropped, and don't crash the walk.
5.3 Filesystem errors
Errors such as permission-denied or dead symlinks are suppressed by default to keep
output clean; --show-errors surfaces them. Operationally, absence of errors does not mean
absence of unreadable paths — enable --show-errors when completeness matters.
5.4 Output and non-UTF-8 paths
fd matches and prints paths as bytes where possible (byte regexes; raw byte output on
Unix non-interactive stdout), so it handles non-UTF-8 filenames faithfully. The --format
path uses lossy UTF-8 conversion for output. Terminal hyperlinks percent-encode all
non-ASCII and control bytes (src/hyperlink.rs), avoiding terminal-escape injection from
crafted filenames in the hyperlink URL.
5.5 No network, no persistence, no privileged operations
fd opens no sockets, writes no files, and requires no elevated privileges. It reads
directory contents and file metadata only. There is no telemetry. The supply-chain surface
is the Rust crate dependency set (see §8) — pin and audit via Cargo.lock, which is
committed.
5.6 Reporting vulnerabilities
The repository ships a SECURITY.md; follow its disclosure process for security reports
rather than filing public issues.
6. Operations
fd is not a service, so "operations" means deployment, integration, and tuning rather
than start/stop/monitor.
6.1 Deployment checklist
- [ ] Choose a distribution method (prebuilt release binary,
cargo install fd-find,cargo binstall, or OS package). - [ ] On Debian/Ubuntu, account for the
fdfindbinary name (provide an alias or symlink). - [ ] Place the binary on
PATH; verify withfd --version. - [ ] (Optional) Install shell completions via
fd --gen-completions <shell>. - [ ] (Optional) Create a global ignore file at
<config-dir>/fd/ignorefor machine-wide defaults. - [ ] If
--list-detailswill be used on Windows, ensure GNUlsis installed.
6.2 Performance tuning
| Lever | Effect |
|---|---|
--threads <N> / -j |
Cap or raise parallelism. Default is min(available_parallelism, 64). Lower it to reduce load or get ordered --exec; raise it rarely helps past core count. |
--max-results / -1 |
Stop early — huge wins when you only need a few hits. |
--max-depth |
Bounds traversal; avoids descending deep trees. |
--prune |
Skips entire matching subtrees. |
-E/--exclude, ignore files |
Prune noisy directories (.git, node_modules) before they're walked. |
| jemalloc feature | Improves allocator throughput under heavy multithreading (default where supported). |
--max-buffer-time (hidden) |
Tune the 100 ms buffer-then-stream threshold for sorted-vs-streaming output. |
Tip
For the fastest scans, combine ignore-based pruning with
--max-depthand, when running commands, prefer-X(batch) over-x(per-result) to minimize process spawns.
6.3 Signal handling
When printing with colors, fd installs a Ctrl-C handler: the first Ctrl+C
sets a quit flag for a clean shutdown; a second forces immediate exit with code 130. On
Unix, fd re-raises SIGINT with the default handler before exiting so the parent shell
sees a genuine interrupted-by-signal status.
6.4 Integration patterns
- Pipelines: use
-0/--print0withxargs -0to handle paths containing spaces or newlines safely. - Scripts/CI: use
-q/--quietfor existence checks (exit 0 = found, 1 = not found). - Editors/tools:
fdis commonly used as the backend file lister for fuzzy finders and editors;--type f,--hidden, and ignore handling make it a good default source.
7. Code Quality & Conventions
- Separation of concerns: parsing (
cli), configuration (config), traversal (walk), filtering (filter,filetypes), formatting (fmt,output), and execution (exec) are cleanly separated.Configis the single immutable hand-off between setup and search. - Cheap-before-expensive filtering: the walker closure orders checks to avoid
statsyscalls on entries that fail a name match — a deliberate performance discipline. - Lazy work:
DirEntrycaches metadata and computed style inOnceCells so they're fetched at most once. - Test coverage: unit tests are colocated with their modules — extensive table-driven
tests for size parsing (
filter/size.rs), time filtering (filter/time.rs, with an injectable clock), owner parsing, format-template generation, path helpers, exit-code merging, and Windows path-separator handling. Integration tests live intests/with aTestEnvharness (tests/testenv/mod.rs). - Cross-platform care:
#[cfg(...)]gates isolate Unix-only (owner, signals, raw byte output) and Windows-only (path separators, UNC handling, ANSI enablement) code paths. - Formatting:
rustfmt.tomland aMakefilestandardize builds and style; CI badges and arelease-checklist.mddocument the release process.
Note
src/output.rscarriesTODO: this function is performance critical and can probably be optimizedmarkers on the hot print paths — a known, intentional optimization target, not a defect.
8. Dependencies
Curated from Cargo.toml. Cargo.lock is committed, pinning the full transitive set.
| Crate | Role in fd |
|---|---|
ignore |
The parallel directory walker and full .gitignore/hidden/override engine. The core fd delegates traversal to. |
clap (derive, suggestions, color, wrap_help, cargo) |
Command-line parsing and help generation. |
clap_complete |
Shell-completion generation (completions feature). |
regex / regex-syntax |
Pattern compilation/matching (byte regexes) and HIR parsing for smart case. |
globset |
Glob → regex translation for --glob and excludes. |
aho-corasick |
Fast multi-pattern scan of format/exec placeholders. |
lscolors (nu-ansi-term backend) |
LS_COLORS-driven output coloring. |
nu-ansi-term |
ANSI styling primitives. |
crossbeam-channel |
Bounded channel carrying result batches from walkers to the receiver. |
jiff |
Date/duration parsing for --changed-within/--changed-before. |
argmax |
Command-length-aware batching for --exec-batch. |
ctrlc |
Cross-platform Ctrl-C handler. |
etcetera |
Locating the platform config directory for the global ignore file. |
normpath |
Robust path normalization. |
faccess |
Executable-bit check for --type executable. |
anyhow |
Ergonomic error propagation in setup code. |
nix (Unix) |
User/group lookup, hostname, signal handling. |
libc (Unix, non-redox) |
Low-level platform bindings. |
tikv-jemallocator (optional) |
jemalloc global allocator on supported targets. |
diff, tempfile, filetime, test-case (dev) |
Test harness support. |
9. Potential Improvements & Known Issues
Drawn from in-code comments and community-reported issues; useful for maintainers and downstream packagers.
Open community issues of operational interest
| Issue | Summary |
|---|---|
| #1965 | Request: support light/dark terminal themes for default colors. |
| #1479 | fd may not exit promptly on pipeline close with full-path search + globbing. |
| #1693 | fd -e <ext> extension matching can also match foo.<ext> appearing in directory names. |
| #1919 | --exclude may only affect the first --search-path in some non-glob cases. |
| #1482 | --list-details fails on Windows when nushell's ls shadows GNU ls. |
| #198 | Request: --exclude-regex for negating patterns by regex. |
| #612 | Discussion: whether to show git-ignored files by default. |
In-code improvement targets
- jemalloc on macOS — currently disabled pending an upstream fix (#498); revisit when
the jemalloc/macOS interaction is resolved. Keep the
Cargo.tomlfeature gate and themain.rsallocatorcfgin sync. src/output.rshot paths — flagged in-source as performance-critical and optimizable; the print functions are the most-executed code in large searches.- Leading-dot detection (
regex_helper.rs) — currently detects only the simplest^\.form; more patterns that exclusively match hidden files could be recognized to improve the "hidden files are filtered" hint.
10. Quick Reference
Build & install
cargo build --release # build (needs Rust ≥ 1.90.0)
cargo install fd-find # install from crates.io (binary: fd)
cargo binstall fd-find # install a prebuilt release binary
Architecture in one line: parse → Config → ignore::WalkParallel sender threads
filter & batch → crossbeam channel → receiver buffers ≤100 ms (sorted) then streams, or
fans out to exec threads → ExitCode.exit().
Exit codes: 0 success / match found · 1 general error / no match (--quiet) ·
130 SIGINT.
Default behaviors: smart case · skip hidden · respect ignore files (in-repo) · match name only · regex pattern · color/ hyperlink in interactive terminals.
No config file. Configure via flags, ignore files (.fdignore, global
<config>/fd/ignore, --ignore-file), and env vars (LS_COLORS, NO_COLOR, MSYSTEM,
TERM).
Key source files: main.rs (orchestration/config), cli.rs (flags), walk.rs
(engine), exec/ (commands), filter/ (size/time/owner), output.rs (printing).