Open-Source Wikis

/

fd

/

How to contribute

/

Patterns and conventions

sharkdp/fd

Patterns and conventions

Recurring patterns to keep in mind when reading or writing fd code.

Module layout

The codebase is flat. src/ has one file per concern, with a few small subdirectories (exec/, filter/, fmt/). There is no lib.rs exposing a public API: fd is built only as a binary. Internal modules are referenced through crate::module_name and pub is used liberally because everything is in the same crate.

Configuration is built once, then read

Opts (src/cli.rs) is the raw clap-parsed input. Config (src/config.rs) is the canonicalised, immutable bag of settings the rest of the program reads. The conversion happens in construct_config in src/main.rs, with all the policy decisions concentrated there:

  • Smart-case decision (pattern_has_uppercase_char).
  • Color decision (terminal detection × NO_COLOR × --color).
  • LsColors resolution (env var, falling back to the bundled DEFAULT_LS_COLORS).
  • CommandSet construction from --exec/--exec-batch/--list-details.

After walk::scan is called, Config is read-only. New flags should follow this pattern: add to Opts, derive in construct_config, expose as a Config field.

Lazy metadata

DirEntry (src/dir_entry.rs) wraps the underlying entry but does not call metadata() eagerly. Both metadata and style are stored in OnceCell<Option<…>>. Filters call entry.metadata() only when they actually need it (e.g., size and time filters), and the result is cached for any subsequent caller (the printer also reads style).

The pattern is "pay for what you use, exactly once":

pub fn metadata(&self) -> Option<&Metadata> {
    self.metadata
        .get_or_init(|| match &self.inner {
            DirEntryInner::Normal(e) => e.metadata().ok(),
            DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
        })
        .as_ref()
}

Backpressure via batched channel sends

Senders accumulate results into a Batch (Arc<Mutex<Option<Vec<WorkerResult>>>>) and only push the first item of a new batch onto the channel. Subsequent items append into the same Vec, which the receiver eventually consumes:

fn send(&mut self, item: WorkerResult) -> Result<(), SendError<()>> {
    let mut batch = self.batch.lock();
    if self.needs_flush(batch.as_ref()) {
        drop(batch);
        self.batch = Batch::new();
        batch = self.batch.lock();
    }
    let items = batch.as_mut().unwrap();
    items.push(item);
    if items.len() == 1 {
        self.tx.send(self.batch.clone()).map_err(|_| SendError(()))?;
    }
    Ok(())
}

This minimises lock contention and channel sends without forcing the senders to block on a slow receiver.

Smart switchover from buffering to streaming

Anywhere fd has to choose between "I want sorted output" and "I want results immediately", the same pattern appears: buffer with a hard limit and a deadline, then switch to streaming. ReceiverBuffer is the canonical example (src/walk.rs). It also explains why fd results are mostly sorted for fast searches but appear in walker order on huge trees.

cfg-gated platform code

Cross-platform code is gated with #[cfg(unix)]/#[cfg(windows)] rather than runtime checks:

  • src/filesystem.rs has parallel #[cfg(unix)] and #[cfg(windows)] implementations of is_block_device, is_char_device, is_socket, is_pipe, and osstr_to_bytes.
  • src/output.rs has separate print_entry_uncolorized definitions to write raw bytes on Unix.
  • src/filter/owner.rs is entirely Unix-only.
  • src/main.rs uses a long target-cfg list to gate tikv-jemallocator.

When adding new functionality, mirror this style. Avoid runtime cfg!() checks inside hot loops.

Error handling

  • Top-level errors propagate as anyhow::Result<...>. run() returns Result<ExitCode> and main formats with {:#} to print the entire chain.
  • Filesystem errors during traversal are swallowed into WorkerResult::Error(ignore::Error) and only surfaced via --show-errors (gated by Config::show_filesystem_errors).
  • User-facing diagnostics use print_error (src/error.rs), which prefixes [fd error]:.
  • Recoverable parse failures (e.g., a malformed user-supplied size constraint) bail out early with anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", s) style messages.

Reuse the FormatTemplate

--exec/--exec-batch and --format share the same template engine (src/fmt/mod.rs). Anything you build that needs to interpolate paths should reuse this engine; do not introduce a parallel placeholder system.

Coding style

  • rustfmt is enforced by CI (.github/workflows/CICD.yml's ensure_cargo_fmt job).
  • rustfmt.toml is intentionally tiny — the project does not customise much beyond the defaults.
  • clippy runs with --all-features -- -Dwarnings. New lints must be addressed, not allowed, unless you have a strong reason.
  • Edition 2024 is used (Cargo.toml). Take advantage of let-chains and the new try_block semantics; recent commits already do.
  • Function-level doc comments are used on most public APIs. Comments tend to explain "why", not "what".

Naming conventions

  • Types use UpperCamelCase: Opts, Config, DirEntry, WorkerResult, CommandSet, FormatTemplate, SizeFilter, TimeFilter, OwnerFilter.
  • Modules are snake_case: walk, filter, fmt, exec, regex_helper, dir_entry, exit_codes, filesystem.
  • Boolean fields are positively named: case_sensitive, null_separator, read_fdignore, read_vcsignore. The CLI exposes --no-foo flags and Opts has hidden bar: () overrides for clap's overrides_with.

Testing convention

Each non-trivial parser or helper has its own #[cfg(test)] mod tests block. Behavioural tests live in tests/tests.rs and use the shared TestEnv harness. See testing.

Public-API surface

There is none. fd is a binary, not a library, so feel free to refactor internal modules freely. The only stable surface is the CLI itself, the documented exit codes (src/exit_codes.rs), and the file formats fd reads (.fdignore, .ignore, .gitignore, the global ignore file).

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – fd wiki | Factory