regex's search.rs carries the highest activity risk — 3 of top 5 hotspots live there

In rust-lang/regex, three of the five highest-risk functions in the whole codebase live in the same two search.rs files, concentrated around forward and overlapping DFA search implementations.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk18.76Low
Hottest Functionfind_fwd_imp

Antipatterns Detected

complex_branching9deeply_nested9exit_heavy9long_function8god_function6stale_complex5cyclic_hub1middle_man1

Run this on your own codebase

See if your own repo has a find_fwd_imp-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is deep nesting and why does it matter in regex?

Nesting depth counts how many levels of if, match, and loop blocks are stacked inside each other before you reach a given line of code — a depth of 8, which is what I found in `find_fwd_imp` in the hybrid search path, means a reviewer has to mentally hold eight layers of context to verify a single branch is correct. In regex's search internals this matters more than usual because several of those nested branches guard `unsafe` byte access, so a mistake in the nesting logic isn't just a wrong answer, it's a potential out-of-bounds read. Nine functions across this analysis triggered the deep-nesting pattern, and several of them are the same functions also flagged for complex branching, meaning the two problems compound rather than existing independently.

How do I reduce cyclomatic complexity in Rust search loops like these?

The standard technique is extract-method: pull a self-contained chunk of branching logic — like the unrolled unsafe loop inside `find_fwd_imp` in `regex-automata/src/dfa/search.rs` — into its own named function with its own safety-invariant comment, so the caller's complexity count drops by however many paths moved with it. A cyclomatic complexity above 15 is worth planning a split for; above 30, which is where two of my top five functions sit (both `find_fwd_imp` variants, at 35 each), I'd treat it as an immediate candidate rather than deferring it. A concrete first step: separate the prefilter-dispatch branch at the top of `find_fwd_imp` from the main unrolled search loop below it — that alone removes one full layer of nesting and several branches from the core loop's count.

Is regex actively maintained?

Yes, with a specific caveat visible in this data: the two `find_fwd_imp` functions each show 1 touch in the last 30 days and were last changed 25 days ago, placing them in active fire-quadrant territory rather than dormant debt. At the same time, three functions in this same top five — `find_overlapping_fwd_imp` in both the hybrid and DFA files, and `find_rev_imp` in the hybrid file — had zero touches in the last 30 days and haven't been changed in 350 days despite carrying comparable complexity, which tells me maintenance attention is currently concentrated on the forward-search path rather than spread evenly. Active development on part of a module and high structural debt in an adjacent part of the same module aren't contradictory findings — they're exactly what I'd expect from a performance-critical core that gets touched carefully and only where needed.

How do I reproduce this analysis?

I used the hotspots CLI, open source on GitHub, against commit 72d650c of rust-lang/regex. After running `git checkout 72d650c`, the exact command is `hotspots analyze . --mode snapshot --explain-patterns --force`, and the same command works unmodified on any local git repository without extra configuration.

What does activity-weighted risk mean?

The activity-weighted risk score multiplies structural complexity — cyclomatic complexity, nesting depth, and fan-out combined — by how frequently a function has been changed recently, so functions that are both hard to follow and actively being edited score highest. That's why `find_fwd_imp` in the hybrid search file, with 1 touch in the last 30 days, scores an 18.76 and lands in the fire quadrant, while the overlapping-search and reverse-search functions with similar raw complexity but zero touches in the last 30 days score lower and land in the debt quadrant instead. The point of weighting it this way is to focus review effort on code where a mistake is most likely to land in the codebase soon, not just on whatever looks the most tangled in the abstract.

Across 3,575 functions and 216 flagged critical, the sharpest concentration of risk in rust-lang/regex sits in the search implementations under regex-automata/src/hybrid/search.rs and its DFA sibling — three of my top five hotspots come from this pair of files, two of them from the hybrid version alone. The top-ranked function, find_fwd_imp in the hybrid search path, carries an activity-weighted risk score of 18.76 and sits in the ‘fire’ quadrant: it had 1 touch in the last 30 days despite a cyclomatic complexity of 35 and a nesting depth of 8, making it a live regression risk rather than a backlog cleanup item. The rest of the top five split between that same live-risk profile and a set of overlapping-search and reverse-search variants that haven’t changed in 350 days but still carry blast-radius complexity that will hit hard whenever someone finally touches them.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
find_fwd_impregex-automata/src/hybrid/search.rs18.835819
find_fwd_impregex-automata/src/dfa/search.rs18.535814
find_overlapping_fwd_impregex-automata/src/hybrid/search.rs16.922717
find_overlapping_fwd_impregex-automata/src/dfa/search.rs16.723712
find_rev_impregex-automata/src/hybrid/search.rs16.124614

<BandChart fire=‘29’ debt=‘538’ watch=‘36’ ok=‘2972’ />

The quadrant split is the first thing worth sitting with: 538 functions across the codebase are sitting in structural debt — complex but currently dormant — versus only 29 in active fire. That ratio tells me the bulk of the review burden here isn’t urgent-today work, it’s a backlog of high-blast-radius code waiting for the next person who has to touch it.

<PatternCloud patterns=‘complex_branching:9,deeply_nested:9,exit_heavy:9,long_function:8,god_function:6,stale_complex:5,cyclic_hub:1,middle_man:1’ />

Every pattern tag in this top-hotspots set points at the same underlying shape: long, deeply nested search loops with many exit points and heavy direct coupling to the DFA and cache types they operate on. Six of the top functions are flagged as god functions, meaning their size and responsibility span is doing more than a single well-named unit should. Nine are flagged deeply nested and nine complex-branching — largely the same five functions triggering multiple pattern detectors at once, which is itself informative: this isn’t a codebase with scattered minor issues, it’s a small number of functions carrying a disproportionate share of the flagged risk.

find_fwd_imp — regex-automata/src/hybrid/search.rs

<FunctionCard fn=‘find_fwd_imp’ file=‘regex-automata/src/hybrid/search.rs’ risk=‘18.76’ band=‘critical’ cc=‘35’ nd=‘8’ fo=‘19’ touches=‘1’ />

This is the top-ranked hotspot in the whole repository, and the source excerpt shows exactly why. It’s the forward-search driver for the hybrid (lazy) DFA — it handles prefilter dispatch, a tagged-vs-untagged state fast path, and a hand-unrolled inner loop with four unsafe blocks doing raw byte indexing via get_unchecked. A cyclomatic complexity of 35 combined with a nesting depth of 8 means a reviewer has to hold eight levels of control flow in their head simultaneously just to verify one branch is correct, and a fan-out of 19 distinct calls means this function is deeply wired into the cache, DFA, and prefilter machinery around it. It had 1 touch in the last 30 days and last changed 25 days ago — recent enough that whatever edit landed there is still fresh, and any regression from it would surface in exactly this kind of high-throughput hot path. The unsafe macro next_unchecked! sits right in the middle of the highest-complexity branch, which in Rust means ownership and bounds-safety reasoning is layered on top of the branching complexity, not separate from it. My recommendation: extract the unrolled unsafe loop into its own clearly-bounded helper with its own safety-invariant documentation block, separate from the prefilter-dispatch logic above it — that alone would cut the effective nesting a reviewer has to track in the main function.

find_fwd_imp — regex-automata/src/dfa/search.rs

<FunctionCard fn=‘find_fwd_imp’ file=‘regex-automata/src/dfa/search.rs’ risk=‘18.51’ band=‘critical’ cc=‘35’ nd=‘8’ fo=‘14’ touches=‘1’ />

This is the non-lazy DFA counterpart to the function above, and the metrics are almost a mirror: same complexity of 35, same nesting depth of 8, one fewer point of fan-out coupling at 14. Reading the excerpt, the unrolling here is even more explicit — four consecutive unsafe next_unchecked! calls chained with early breaks on special-state checks, each one advancing the cursor by one byte. That’s a textbook exit-heavy pattern: multiple early-return and break paths inside a hot loop, each one a distinct execution path that needs its own test case to be confident in. Like its hybrid sibling, it had 1 touch in the last 30 days and last changed 25 days ago, keeping it in the fire quadrant. Because these two functions are near-duplicates of the same algorithm across two DFA implementations, I’d treat them as a pair for any refactor — extracting the shared unrolling pattern into a common macro or generic helper would cut duplicated review effort roughly in half rather than fixing one and leaving its twin untouched.

find_overlapping_fwd_imp — regex-automata/src/hybrid/search.rs

<FunctionCard fn=‘find_overlapping_fwd_imp’ file=‘regex-automata/src/hybrid/search.rs’ risk=‘16.91’ band=‘critical’ cc=‘22’ nd=‘7’ fo=‘17’ touches=‘0’ />

Here the picture changes. This function had zero touches in the last 30 days and hasn’t changed in 350 days, which puts it squarely in structural debt rather than live risk. But the complexity hasn’t gone anywhere: cyclomatic complexity of 22, nesting depth of 7, and a fan-out of 17 distinct calls into the cache and DFA state machine. The excerpt shows a state machine inside a state machine — an outer match on state.id to decide whether this is a fresh search or a resumed one, then an inner loop checking tagged states, start states, match states, dead states, and quit states each with their own branch. That’s a lot of exit conditions for a function that also has to coordinate mutable state across calls (OverlappingState). Because it’s dormant, the near-term regression risk is low, but the next person who needs to add a new state-handling case here inherits all 22 paths at once with no recent history to lean on for context. I’d flag this as a candidate for decompose-conditional before it’s touched again — splitting the resume-state handling from the main search loop would make the next change safer and faster to review.

find_overlapping_fwd_imp — regex-automata/src/dfa/search.rs

<FunctionCard fn=‘find_overlapping_fwd_imp’ file=‘regex-automata/src/dfa/search.rs’ risk=‘16.69’ band=‘critical’ cc=‘23’ nd=‘7’ fo=‘12’ touches=‘0’ />

The non-lazy-DFA twin of the function above tells the same story: zero touches in the last 30 days, 350 days since it last changed, cyclomatic complexity of 23 and nesting depth of 7. The excerpt shows the same overlapping-match resume logic, but with an added acceleration path — a branch that calls accel::find_fwd to skip ahead using a needle-based accelerator when the state supports it. That’s an extra execution path layered on top of an already branch-heavy function, and it’s why the pattern detector flags this as both a god function and stale-complex: complexity has accumulated but hasn’t been revisited in nearly a year. Given that this and the hybrid version above share the same overlapping-match algorithm shape, I’d treat any refactor of one as an opportunity to reconcile both — right now a bug found in one implementation’s state-handling has no guarantee of being checked against its counterpart.

find_rev_imp — regex-automata/src/hybrid/search.rs

<FunctionCard fn=‘find_rev_imp’ file=‘regex-automata/src/hybrid/search.rs’ risk=‘16.07’ band=‘critical’ cc=‘24’ nd=‘6’ fo=‘14’ touches=‘0’ />

The reverse-search counterpart to the forward-search functions above rounds out the picture of this file: three of my five hotspots now come from hybrid/search.rs alone. Cyclomatic complexity of 24 and a fan-out of 14, with zero touches in the last 30 days and 350 days since last change — another debt-quadrant function. The excerpt shows the same unrolled-unsafe-loop pattern as the forward version, adapted for backward iteration with its own empty-input special case handled via a separate eoi_rev call before the main loop even starts. That special-casing at the top is itself a signal: reverse search needs different edge-case handling than forward search, and that logic currently lives inline rather than being isolated. Given that this file now accounts for three of the five riskiest functions in the repository — two live, one dormant — I’d treat hybrid/search.rs as a unit for review rather than picking off individual functions. Anyone assigned to touch overlapping or reverse search in this file should budget time to read all three functions together, since the unrolling pattern and state-machine shape repeat across them.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching9
deeply_nested9
exit_heavy9
long_function8
god_function6
stale_complex5
cyclic_hub1
middle_man1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, cyclic_hub, deeply_nested, exit_heavy, god_function, long_function, middle_man, stale_complex.

Reproduce This Analysis

git clone https://github.com/rust-lang/regex
cd regex
git checkout 72d650cb0a880a01ab6dc2137c0888e8f89740f7
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses