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
- Add the state explicitly to the attribute, pointing at the type passed to Router::with_state: #[debug_handler(state = AppState)].
- 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.
- 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
- Whenever a handler takes more than one State<T>, set #[debug_handler(state = RootState)] explicitly — do not rely on inference.
- Prefer a single State<RootState> argument and obtain sub-states through FromRef<RootState> instead of multiple State<_> extractors.
- Run debug/test builds often: #[debug_handler] is a no-op under the release profile, so state-inference mistakes only surface in debug.
- Keep the type passed to Router::with_state as the single source of truth and reference it verbatim in state = ....
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
- can't infer state type, please add #
- missing #[from_request(via(...))]
- Missing path: #[typed_path("/foo/bar")]
- Typed paths for unit structs cannot contain captures
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)