tokio-rs/axum · error · syn::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] attribute macro (debug_handler.rs:38). To type-check a handler, the macro must know the router's state type. It tries to infer it by scanning the fn arguments for axum::extract::State<T> wrappers (state_types_from_args -> infer_state_types). If exactly one distinct inner type is found it uses it; if two or more different inner types are found it cannot decide which is the root state and emits this compile error, asking you to set it explicitly with state = .... Inference is skipped entirely for generic functions (a different error is emitted there).

Solutions

  1. Add the state explicitly to the attribute, pointing at the type passed to Router::with_state: #[debug_handler(state = AppState)].
  2. If one of the State<_> arguments is really a sub-state, remove it and obtain the sub-state via FromRef<AppState> from the single root State<AppState> argument.
  3. If unsure which type is the root state, remove the macro temporarily to read the underlying Handler trait error, then re-add it with the explicit state.

Example fix

// before
#[debug_handler]
async fn handler(
    State(a): State<AppState>,
    State(b): State<InnerState>,
) {}

// after
#[debug_handler(state = AppState)]
async fn handler(
    State(a): State<AppState>,
    State(b): State<InnerState>,
) {}

// (InnerState is obtained via `impl FromRef<AppState> for InnerState`)
Defensive patterns

Strategy: validation

Validate before calling

// Safe invocation pattern: a handler with multiple State<_> extractors MUST
// declare the root state explicitly. Use this form whenever len(distinct State inner types) > 1.
//
// #[debug_handler(state = <type passed to Router::with_state>)]
#[debug_handler(state = AppState)]
async fn handler(
    State(a): State<AppState>,
    State(b): State<InnerState>, // InnerState: impl FromRef<AppState>
) {}

// One-state handlers infer automatically and need no `state = ...`:
#[debug_handler]
async fn simple(State(s): State<AppState>) {}

Prevention

When it happens

Trigger: A non-generic async fn decorated with #[debug_handler] (or #[debug_middleware]) that takes two or more State<T> arguments whose inner T differs (e.g. State(a): State<AppState> and State(b): State<InnerState>), and the attribute does NOT set state = .... The macro collects the distinct state types into a HashSet, finds len() > 1, and returns the error via err.unwrap_or_else.

Common situations: Using nested/sub-state via FromRef where a handler pulls both the root AppState and a sub-state InnerState through two separate State<_> extractors. Refactoring that introduces a second State<_> argument. Copy-pasting a handler from a single-state router into a multi-state one. Forgetting that the macro infers from the argument list, not from Router::with_state.

Related errors


AI-assisted analysis of tokio-rs/axum@151cd5c123 (2026-08-11). Data as JSON: /api/errors/45e96cc3b186a075. Report an issue: GitHub.

Appendix: 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 151cd5c123)