Dependency Philosophy
On this page
Status
Accepted
Context
Ferroni is a pure-Rust regex engine that ships as a single crate. The question is how many third-party dependencies to take on, and under what criteria.
Decision
Ferroni uses four runtime dependencies without features, each chosen because it solves a specific problem better than hand-rolling, and two more behind the default dfa-prefilter feature (see the amendment below):
Scroll sideways for more columns.
| Crate | Purpose | Transitive deps |
|---|---|---|
aho-corasick | Aho-Corasick automaton for single-pass literal alternation search | 1 (memchr) |
bitflags | Type-safe option flags replacing type OnigOptionType = u32 | 0 |
memchr | SIMD-vectorized byte search for the forward-scan pipeline (ADR-007) | 0 |
smallvec | Stack-allocated small vectors for scanner captures (internal) | 0 |
Each was selected because:
- Zero or near-zero transitive dependencies. No dependency tree surprises.
- Battle-tested in the Rust ecosystem. Used by
regex,ripgrep,serde, and other foundational crates. - Solves a specific problem better than hand-rolling.
aho-corasickprovides a production-grade Aho-Corasick automaton with Teddy SIMD prefilters for single-pass multi-pattern matching — hand-rolling this correctly for all architectures is non-trivial;memchrprovides SIMD dispatch across architectures;bitflagsprovides compile-time flag safety;smallveceliminates heap allocation for the common case. - No feature creep. Each crate does one thing.
Amendment: regex-automata and regex-syntax
The scanner's DFA pre-filter (ADR-008) builds, per set of patterns, a multi-pattern lazy DFA and a meta regex from an over-approximation of each pattern. These come from two more crates, behind the dfa-prefilter feature, which is on by default:
Scroll sideways for more columns.
| Crate | Purpose | Transitive deps |
|---|---|---|
regex-automata | Thompson NFA, lazy DFA and meta regex over the seek approximations; features std, syntax, meta, nfa-thompson, hybrid, and perf-literal and perf-inline for the meta regex's literal prefilters (through memchr and aho-corasick) | aho-corasick, memchr, regex-syntax (all already present) |
regex-syntax | The HIR the approximations are written in; features std only | 0 |
Against the criteria above:
- Transitive dependencies: none new.
regex-automatadepends onaho-corasickandmemchr, which Ferroni already uses, and onregex-syntax. Neither crate needs its Unicode tables here: the approximations are byte-based and use ASCII look-arounds only, so theunicode-*features stay off. - Battle-tested: they are the
regexcrate's engine and parser, split out so that they can be used without its public API, and are whatripgrepand theregexcrate itself run on. - Solves a specific problem better than hand-rolling: an overlapping lazy DFA over hundreds of patterns, with cache management, start-state handling for look-around context and a leftmost-first meta engine with its own prefilters, is the
regexcrate's core competence; writing one inside Ferroni would duplicate it with less testing. - No feature creep: each does one thing, and the pre-filter uses them for that one thing.
The feature exists so that the dependency stays a choice: default-features = false drops both crates, and the scanner then runs the position-lead search alone, with the same results. ScannerConfig::prefilter(false) switches the pre-filter off per scanner without changing the dependency graph.
Dev dependencies
Scroll sideways for more columns.
| Crate | Purpose |
|---|---|
criterion | Benchmark framework and local regression measurement |
serde_json | Parsing Shiki grammar JSON in benchmarks |
cc | Building C Oniguruma for benchmark comparison (optional, behind ffi feature) |
The cc crate is gated behind the ffi feature and only used for benchmark comparisons -- it is never part of a normal cargo build.
Rationale
Every dependency is a trust decision. A convenience crate that saves 20 lines of code adds audit burden, supply-chain risk, and a potential MSRV constraint. Ferroni's target audience (syntax highlighters, editors, language tooling) values reliability over developer convenience.
Consequences
- Adding a new runtime dependency requires explicit justification against the criteria above; the amendment for
regex-automataandregex-syntaxis the form such a justification takes. - The total dependency tree for
cargo build(no features) stays minimal and auditable; with the default features it adds the two crates above and nothing transitive. - No framework-style dependencies, no proc-macro dependencies at runtime, no
build.rsin normal builds.