tokio-rs/axum · error · compile_error

can't infer state type, please add set it explicitly, as in

Error message

can't infer state type, please add set it explicitly, as in `#[axum_macros::debug_{kind}(state = MyStateType)]`

What it means

Thrown by the `#[debug_handler]` / `#[debug_middleware]` macros when, after scanning the handler's arguments, the macro finds more than one distinct state type (i.e. more than one `State<T>` with different `T`s) and therefore cannot decide which single type to use as the `S` parameter for the `FromRequestParts<S>` / `FromRequest<S>` checks it generates. Because the macro emits real compile-time trait checks against this inferred state, an ambiguous guess would produce misleading downstream errors, so it bails early and asks the developer to name the state explicitly via `state = MyStateType`.

Source

Thrown at axum-macros/src/debug_handler.rs:38

        check_output_impls_into_response(item_fn)
    } else {
        check_output_tuples
    };

    // If the function is generic, we can't reliably check its inputs or whether the future it
    // returns is `Send`. Skip those checks to avoid unhelpful additional compiler errors.
    let check_inputs_and_future_send = if item_fn.sig.generics.params.is_empty() {
        let mut err = None;

        if state_ty.is_none() {
            let state_types_from_args = state_types_from_args(item_fn);

            #[allow(clippy::comparison_chain)]
            if state_types_from_args.len() == 1 {
                state_ty = state_types_from_args.into_iter().next();
            } else if state_types_from_args.len() > 1 {
                err = Some(
                    syn::Error::new(
                        Span::call_site(),
                        format!(
                            "can't infer state type, please add set it explicitly, as in \
                            `#[axum_macros::debug_{kind}(state = MyStateType)]`"
                        ),
                    )
                    .into_compile_error(),
                );
            }
        }

        err.unwrap_or_else(|| {
            let state_ty = state_ty.unwrap_or_else(|| syn::parse_quote!(()));

            let check_future_send = check_future_send(item_fn, kind);

            if let Some(check_input_order) = check_input_order(item_fn, kind) {
                quote! {

View on GitHub (pinned to c9a911b799)

Solutions

  1. Add an explicit state type to the attribute, e.g. `#[debug_handler(state = AppState)]`, naming the top-level router state that yields every other sub-state through `FromRef`.
  2. Collapse the multiple `State<T>` arguments into a single `State<AppState>` and read sub-state via `FromRef` inside the handler body, so only one state type is inferable.
  3. If the second `State<T>` is actually a different extractor in disguise, switch it to a non-`State` extractor (e.g. `Extension<T>` or a custom `FromRequestParts`) so it no longer participates in state inference.
  4. Remove `#[debug_handler]` temporarily to confirm the handler compiles, then re-add it with the explicit `state = ...` once the real state type is known.

Example fix

// before
#[debug_handler]
async fn handler(db: State<Db>, cfg: State<Cfg>) {}

// after
#[debug_handler(state = AppState)]
async fn handler(db: State<Db>, cfg: State<Cfg>) {}
Defensive patterns

Strategy: validation

Validate before calling

// Before annotating, confirm exactly one State<T> appears (or name the state).
// In a separate module, a quick lint helper (nightly-only clippy-style):
//
// manual check: grep your handler signature for `State<` occurrences;
// count distinct inner types. If >1, you MUST pass `state = ...`.
//
// Example assertion that documents the intended single state type:
fn _assert_single_state(_a: &State<AppState>, _b: &State<AppState>) {}
// If the handler actually takes State<OtherState>, this fn's call site
// fails to compile, surfacing the mismatch before #[debug_handler] does.

Type guard

// Compile-time guard: force every State<_> in the handler to share the same inner type.
// (Forces a clear error at the call site rather than inside macro expansion.)
trait SameState {}
impl SameState for () {}
fn require_single_state<S>() where S: SameState {}

// Usage in tests:
#[test]
fn handler_state_is_unambiguous() {
    // If you ever add State<OtherState>, this stops compiling once you
    // add a corresponding bound here.
    require_single_state::<AppState>();
}

Prevention

When it happens

Trigger: A non-generic handler annotated with `#[debug_handler]` (or `#[debug_middleware]`) takes two or more `State<...>` arguments whose inner types differ (e.g. `State<AppState>` and `State<OtherState>`) AND the `state = ...` key is not present on the attribute. The branch is reached at debug_handler.rs:36 (`state_types_from_args.len() > 1`) because `infer_state_types` (lib.rs:758) collects every `State<T>` it can see in the fn signature and finds >1 distinct type. Note this path only runs when `state_ty.is_none()` (line 30) and the function has no generics (line 27); generic handlers fail with a different "doesn't support generic functions" error.

Common situations: Refactoring a handler to depend on substates via `FromRef` (e.g. `State<Database>` + `State<Config>`) without realizing `#[debug_handler]` can only target one router state. Copying a handler from a router with a single shared `AppState` into a router whose state is split into several `FromRef` providers. Adding a second `State<...>` extractor while leaving the existing `#[debug_handler]` (no `state` key) in place. Most often a code-organization change rather than an upgrade/env issue.

Related errors


AI-assisted analysis of tokio-rs/axum@c9a911b799 (2026-08-06). Data as JSON: /data/errors/dc3183732d304777.json. Report an issue: GitHub.