actix-web is a widely used Rust web framework. This analysis covers 2,608 functions, 75 of which land in the critical band. The top risk isn’t a function currently being pulled apart by commits — ‘poll_response’ in ‘actix-http/src/h1/dispatcher.rs’ carries an activity-weighted risk score of 17.68 with zero touches in the last 30 days and 32 days since its last change, which puts it squarely in the debt quadrant: high structural complexity, currently dormant. That’s the frame for most of this list — four of the five top functions are debt, not fire, meaning the risk is stored energy rather than a live incident.
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
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
poll_response | actix-http/src/h1/dispatcher.rs | 17.7 | 68 | 6 | 19 |
parse_path | actix-files/src/path_buf.rs | 17.6 | 29 | 11 | 10 |
new | actix-web-codegen/src/route.rs | 16.8 | 22 | 8 | 6 |
poll | actix-http/src/h1/dispatcher.rs | 16.7 | 17 | 7 | 20 |
poll | actix-http/src/h2/dispatcher.rs | 16.4 | 20 | 6 | 13 |
2,608 functions analyzed
257 functions sit in the debt quadrant against only 17 in fire. That ratio is the headline: actix-web’s structural risk is concentrated in code that isn’t being touched right now, which is a different problem than a codebase actively destabilizing itself. The danger is deferred, not absent — someone will eventually have to modify these functions, and when they do, the blast radius is large.
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Complex Branching×9Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.God Function×7God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×7Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Deeply Nested×6Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Hub Function×1Hub Function
Many other functions call this one — a change here ripples widely through callers.
Across the top hotspots, exit-heavy control flow (10 occurrences) and complex branching (9) dominate. Combined with 7 god-function and 7 long-function flags, the shape here is large state-machine-style functions with many early returns — expensive to unit test exhaustively, and easy to regress silently when one branch is edited.
poll_response — actix-http/src/h1/dispatcher.rs
This is the highest-risk function in the repository, and it’s debt, not fire — 0 touches in the last 30 days, 32 days since its last change. A cyclomatic complexity of 68 is extreme by any threshold; the source excerpt shows a ‘res: loop’ driving a state-machine match over ‘StateProj’ variants (‘None’, ‘ServiceCall’, and others visible in the excerpt), each arm handling a different combination of pipelined requests, expect-continue headers, upgrade requests, and service-call poll results. A max nesting depth of 6 and fan-out of 19 mean this function reasons about pipelining, keep-alive flags, payload draining, and service dispatch all in one place. This file shows 4 total commits with a bug-fix fraction of 0.25 and 2 distinct authors in the last 90 days — modest history, not a red flag on its own, but combined with a review-comment density of 7.0 on the file it suggests reviewers already find this code hard to reason about. My recommendation: before the next feature touches this function, extract the ‘StateProj::None’ message-popping branch and the ‘ServiceCall’ poll-result handling into named helper functions. That alone would cut the branching surface without touching behavior, and it de-risks whichever engineer eventually has to modify pipelining logic here.
parse_path — actix-files/src/path_buf.rs
A nesting depth of 11 is the standout number here — the deepest nesting in the top five, on a function that hasn’t been changed in 105 days. The excerpt shows why: an ‘if’/‘else if’ chain checking each path segment against a dozen distinct rejection rules (leading dots, leading asterisks, trailing colons, trailing angle brackets, backslashes on Windows, empty segments) before finally pushing a valid segment onto the buffer. This is path-traversal-guard logic, which makes correctness here security-relevant, not just a style concern. Cyclomatic complexity of 29 with only 2 historical commits (a bug-fix fraction of 0.5, meaning one of those two commits was a fix) tells me this code was written once and has been trusted since. My recommendation: convert the segment-validation chain into a small table of predicate/error pairs iterated in a loop, or extract a ‘validate_segment’ helper — either would collapse the 11-level nesting into something a reviewer can audit in one pass, which matters more here than in most functions given what it’s guarding against.
new — actix-web-codegen/src/route.rs
This is proc-macro argument parsing for the ’#[route]’ / ’#[get]’ family of attribute macros, and it hasn’t been changed in 106 days. The excerpt shows a long ‘if nv.path.is_ident(…) { … } else if …’ chain matching against ‘name’, ‘guard’, ‘wrap’, and ‘method’ attribute keys, with nested pattern matches on ‘syn::Expr::Lit’ inside each branch to extract the literal value or return a ‘syn::Error’. Nesting depth of 8 comes directly from that literal-matching-inside-attribute-matching structure repeated four times. Because this runs at compile time inside a proc macro, a bug here breaks compilation for every user of the attribute macros, not just a runtime request path — a different kind of blast radius than the dispatcher functions above. My recommendation: extract a shared ‘expect_str_literal(nv) -> syn::Result<LitStr>’ helper to collapse the four near-identical literal-extraction blocks into one call site each, cutting the branching count roughly in half.
poll — actix-http/src/h1/dispatcher.rs
The second dispatcher function in the top five, and the fan-out here — 20 distinct calls — is the highest of any function on this list. The excerpt shows this ‘poll’ implementation branching on ‘DispatcherStateProj’ (‘Upgrade’ vs ‘Normal’), then inside the ‘Normal’ arm sequentially driving graceful shutdown, timers, linger state, shutdown state, and I/O read availability, each a call into a different internal method (‘poll_graceful_shutdown’, ‘poll_timers’, ‘poll_linger’, ‘poll_flush’, ‘read_available’). That’s a coordination function: it doesn’t do much work itself, it orchestrates many other pieces of dispatcher state. Same file, same debt quadrant, 32 days since its last change, the same 2 authors in the last 90 days and the same 0.25 bug-fix fraction as ‘poll_response’ above — this reads as one file carrying two of the five riskiest functions in the repository. Given the shared fan-out into ‘poll_response’, I’d treat these two functions as a single refactoring unit rather than fixing them independently.
poll — actix-http/src/h2/dispatcher.rs
This one differs in kind from the other four: it’s in the fire quadrant, with 1 touch in the last 30 days and only 9 days since its last change. Its activity-weighted risk score of 16.45 sits close behind the dormant dispatcher functions, but is arrived at through active modification rather than accumulated debt — this is the one function on the list where ‘actively changing’ and ‘live regression risk’ are the right framing. The excerpt shows an accept loop spawning a ‘tokio::task::spawn_local’ per incoming HTTP/2 request, with response handling and a nested match over ping-pong keep-alive state (in-flight pong polling vs. sending a new ping) inside the ‘Poll::Pending’ arm. Cyclomatic complexity of 20 and nesting depth of 6 are lower than the H1-side functions, but the fact that this is actively being edited means any complexity here has a shorter feedback loop to production than the dormant functions above. My recommendation: if this function keeps changing, extract the ping-pong keep-alive state machine into its own polled sub-component now, before more logic accumulates around the spawn_local block — waiting until it’s this file’s fifth god-function candidate costs more than doing it while the diff is still small.
Worth noting from the context-only data: ‘send_body’ (awc/src/client/h2proto.rs), ‘send_request’ (awc/src/client/h1proto.rs), and ‘connect’ (awc/src/ws.rs) are all fire-quadrant with 1 touch in the last 30 days apiece — the client-side (awc) request path is seeing real-time iteration in parallel with the server-side dispatcher debt. That’s a useful signal for planning: if the team is actively reworking the client transport layer, it’s a reasonable moment to also schedule the dormant dispatcher debt for the same review cycle rather than treating them as unrelated backlogs.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 10 |
complex_branching | 9 |
god_function | 7 |
long_function | 7 |
deeply_nested | 6 |
hub_function | 1 |
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, deeply_nested, exit_heavy, god_function, hub_function, long_function.
Reproduce This Analysis
git clone https://github.com/actix/actix-web
cd actix-web
git checkout f8c80cd8b8887ee111b386025a0fdf6d6522d4ef
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 →