Across 1,596 functions analyzed in tokio-rs/axum, 18 land in the critical band, and the top of that list is dominated by one file: ‘axum-macros/src/from_request/mod.rs’ supplies three of my top five. The highest-scoring function there, ‘expand’, carries an activity-weighted risk score of 13.63, but it’s a debt-quadrant finding, not a live-churn one. It hasn’t been changed in 178 days, so the risk is what happens the next time someone has to touch derive-macro logic that hasn’t been exercised by a commit in half a year. I’d treat this file as a unit of review, not three isolated functions.
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 |
|---|---|---|---|---|---|
expand | axum-macros/src/from_request/mod.rs | 13.6 | 27 | 3 | 12 |
parse_single_generic_type_on_struct | axum-macros/src/from_request/mod.rs | 13.5 | 26 | 4 | 4 |
strip_prefix | axum/src/routing/strip_prefix.rs | 12.5 | 15 | 4 | 6 |
run | axum/benches/benches.rs | 12.0 | 13 | 2 | 14 |
impl_enum_by_extracting_all_at_once | axum-macros/src/from_request/mod.rs | 11.0 | 14 | 3 | 7 |
expand — axum-macros/src/from_request/mod.rs
This is the entry point for axum’s ’#[derive(FromRequest)]’ macro expansion, and the excerpt confirms what the metrics suggest: a large match over ‘syn::Item::Struct’ versus ‘syn::Item::Enum’, each branch destructuring fields, inferring state types from field attributes, and threading rejection/state logic through nested conditionals. Cyclomatic complexity of 27 with a fan-out of 12 means this one function is coordinating a lot of decision points and calling out to a lot of helpers: ‘parse_single_generic_type_on_struct’, ‘infer_state_type_from_field_types’, ‘impl_struct_by_extracting_all_at_once’, and more. It’s flagged as ‘exit_heavy’, ‘god_function’, and ‘long_function’ simultaneously, which lines up with what I see in the source: multiple early returns via ’?’, a large struct-destructure block, and inline error construction mixed with code generation. None of that is a defect, the two historical commits on this file include one bug fix, a thin signal given the low volume, and no linked bugs or reverts show up. But it hasn’t been touched in 178 days by any recent contributor, so the next person who needs to add a new attribute or state-inference rule is working against a function nobody currently holds context for. My recommendation: before the next feature lands here, split the struct-handling branch and the enum-handling branch into two separate functions. That alone would cut the effective complexity per function roughly in half without changing behavior.
parse_single_generic_type_on_struct — axum-macros/src/from_request/mod.rs
Second in the file, and it’s doing validation work: checking for disallowed ‘where’ clauses, rejecting lifetime and const generics, and enforcing that generic tuple structs have exactly one field of the matching type. The nesting depth of 4 is the standout number here, the source excerpt shows a match on ‘generics.params.len()’ nested inside a match on the generic param kind, nested inside a match on ‘syn::Fields’, each arm returning a distinct ‘syn::Error’. That’s a lot of paths to reason about for what is fundamentally a set of compile-time guard clauses. It’s tagged ‘complex_branching’, ‘exit_heavy’, and ‘long_function’, and the fan-out of 4 is comparatively low, meaning the risk here isn’t coupling, it’s the sheer number of branches a reader has to hold in their head. Same commit history as ‘expand’: two commits, one of them a bug fix, zero authors in the last 90 days, 178 days since last change. Concrete step: decompose-conditional here is a natural fit, pull each ‘syn::GenericParam’ arm into a small named-check function so the outer match reads as a list of validation rules rather than a nested tree.
strip_prefix — axum/src/routing/strip_prefix.rs
This one sits on the routing hot path: it strips a matched prefix (including path parameters) off a request URI before handing the remainder to a nested router. The nesting depth of 4 combined with a cyclomatic complexity of 15 shows up directly in the source: a ‘for’ loop over zipped path and prefix segments, with a ‘match’ inside it on ‘Item::Both’ / ‘Item::First’ / ‘Item::Second’, and an inner ‘if/else’ deciding whether a segment matches or the prefix has ended in a trailing slash. It’s tagged ‘complex_branching’ and ‘exit_heavy’, which tracks, there are three distinct break-out points inside the loop plus the final ‘matching_prefix_length?’ early return. This is exactly the kind of function where correctness matters more than most: it’s on the request path for every prefix-stripped route, and off-by-one segment boundary errors here would be subtle. This is a debt-quadrant finding, not an active-churn one: it hasn’t changed in 167 days, and it shows one author across its three total commits, with no bug-fix or revert history recorded. I’d add explicit unit tests for the trailing-slash and mismatched-segment-count branches before refactoring, then extract the segment-matching loop into its own named function so the URI-reconstruction logic at the bottom stands alone.
run — axum/benches/benches.rs
This is benchmark harness code: it spins up a Tokio runtime, binds a listener, spawns the server, then shells out to ‘rewrk’ with a pile of conditionally-assembled CLI arguments (‘—connections’, ‘—threads’, ‘—duration’, optional method/headers/body). The fan-out of 14 is the number that stands out, this function directly touches the Tokio runtime builder, the listener, thread spawning, and ‘Command’ construction, a lot of direct dependencies for one function even though its nesting is shallow. It’s flagged ‘exit_heavy’ and ‘god_function’, the early ‘return’ when benchmark names don’t match the CLI filter, plus the ‘std::process::exit’ call if the ‘rewrk’ subprocess fails, are the two exit points doing the heavy lifting on that tag. It sits in the debt quadrant at 178 days since its last change with a single historical commit and no recorded bug or revert signal, expected for benchmark scaffolding since it’s not part of the request path. I’d deprioritize this relative to the routing and macro functions above, but if someone extends the benchmark matrix, splitting argument-assembly from process-spawning would make the next addition easier to test.
impl_enum_by_extracting_all_at_once — axum-macros/src/from_request/mod.rs
The third function from the same macro file, this one generates the trait implementation for enum types under ’#[derive(FromRequest)]’. It iterates variants and fields validating that ‘via(…)’ attributes aren’t misapplied, then branches on ‘Trait::FromRequest’ versus ‘Trait::FromRequestParts’ to emit different ‘quote_spanned!’ blocks. Cyclomatic complexity of 14 is more moderate than its two siblings above, but it shares the same ‘long_function’ tag and the same file-level history: two commits, one of them a bug fix, zero authors in the last 90 days, 178 days since anything changed. Given that this is the third complex function in the same file, I’d treat the whole module as one refactoring unit rather than patching each function independently. The struct-handling and enum-handling code paths look structurally parallel (attribute parsing, rejection-type resolution, trait-impl generation), which suggests a shared helper could collapse duplicated branching across ‘expand’ and this function at the same time.
1,596 functions analyzed
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Long Function×4Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.God Function×2God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Complex Branching×2Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Stepping back from the top five: none of them are in the fire quadrant. All five sit in the debt quadrant, dormant between 167 and 178 days, which is unusual for a top-risk list. It means the highest structural complexity in axum right now is concentrated in code that isn’t being exercised by commits, not in code under active churn. The context data tells the opposite story for a different part of the codebase: ‘nest’ and ‘merge’ in ‘axum/src/routing/path_router.rs’, plus ‘insert_url_params’ and ‘write_buf’, all sit in the fire quadrant with a touch in the last 30 days and changes as recent as 0 to 27 days ago, and lower complexity (cyclomatic complexity 7-14). Those are worth a lighter, faster review pass, not a refactor, they’re active and simple enough that the main risk is regression from rapid iteration, not comprehension cost. The concentration of ‘exit_heavy’ (4 occurrences) and ‘long_function’ (4 occurrences) across the top five is the pattern I’d flag: multiple early returns plus long function bodies is a specific, fixable combination, and three of the four instances share one file.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 4 |
long_function | 4 |
god_function | 2 |
complex_branching | 2 |
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, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/tokio-rs/axum
cd axum
git checkout bc8d4912435204a4a073cea70355ae0328d263fe
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 →