Rust in Everything
基于 axum 0.8.9

expected Router, found Router<AppState>

主线位置:装配主线上 with_state 那一步。Router<S> 的 S 从 handler 的 State<..> 参数一路推上来,with_state 把它换成新的类型;类型对不上,编译器就在下一个需要具体类型的地方报错。

flowchart TB
  h["handler:State AppState"] --> mr["get:MethodRouter AppState"]
  mr --> r1["route:Router AppState"]
  r1 --> ws["with_state(state)"]
  ws --> r0["Router:S 由上下文选"]
  r0 --> serve["serve、oneshot:只认 Router"]
  r1 -->|"没有 with_state"| err["类型不匹配"]
  err --> e1["nest、返回值处"]
  err --> e2["get(handler) 处"]
  err --> e3["oneshot 处"]

Router<S> 里的 S 不是「路由器持有的状态」,而是「还缺的状态」,只有 Router<()> 实现了 tower Service,这些在 状态:State 与 FromRef 里讲过,也给过 axum::serve 一个 Router<AppState> 时的报错。这一页把同一个错误在不同位置的几种样子放在一起,因为报错的位置往往离真正写错的地方有好几行。

复现:同一个错误,四个位置

下面的程序都共用这几行:

use axum::{Router, extract::State, routing::get};

#[derive(Clone)]
struct AppState {
  name: String,
}

async fn hello(State(state): State<AppState>) -> String {
  format!("hello from {}", state.name)
}

依赖只有 axum = "=0.8.9"(第 4 种另加 tokio、tower 的 util)。M2 Max、rustc 1.99.0,报错里 registry 的路径缩短成了 axum-0.8.9/...。

1. 子路由忘了 with_state,nest 进 Router。 子路由的返回类型诚实地写了 Router<AppState>,父路由返回 Router:

fn api() -> Router<AppState> {
  Router::new().route("/hello", get(hello))
}

fn app() -> Router {
  Router::new().nest("/api", api())
}
error[E0308]: mismatched types
   --> src/bin/st_nest.rs:17:30
    |
 17 |   Router::new().nest("/api", api())
    |                 ----         ^^^^^ expected `Router`, found `Router<AppState>`
    |                 |
    |                 arguments to this method are incorrect
    |
    = note: expected struct `Router<()>`
               found struct `Router<AppState>`
note: method defined here
   --> axum-0.8.9/src/routing/mod.rs:209:12
    |
209 |     pub fn nest(self, path: &str, router: Router<S>) -> Self {
    |            ^^^^

nest 要求子路由和自己欠同一种状态。父路由的 S 由返回类型定成了 (),子路由欠的是 AppState。

2. 子路由已经 with_state 了,返回类型却还写 Router<AppState>。 报错和第 1 种一字不差:

fn api(state: AppState) -> Router<AppState> {
  Router::new()
    .route("/hello", get(hello))
    .with_state(state)
}

fn app(state: AppState) -> Router {
  Router::new().nest("/api", api(state))
}
error[E0308]: mismatched types
   --> src/bin/st_twice.rs:19:30
    |
 19 |   Router::new().nest("/api", api(state))
    |                 ----         ^^^^^^^^^^ expected `Router`, found `Router<AppState>`

(后面几行与第 1 种相同。)这次状态已经给了,api 本身能编译:with_state<S2> 返回 Router<S2>,S2 由返回类型选成了 AppState,意思是「给完这份 AppState 之后,还欠一份 AppState」。错的是返回类型,修法和第 1 种正好相反:第 1 种要补 with_state,这一种要把返回类型改成 Router 或泛型的 Router<S>。

3. 在返回 Router 的函数里直接用 State,或者 with_state 写在 route 前面。 这两种报的是 MethodRouter:

fn app() -> Router {
  Router::new().route("/", get(hello))
}
error[E0308]: mismatched types
   --> src/bin/st_return.rs:13:28
    |
 13 |   Router::new().route("/", get(hello))
    |                 -----      ^^^^^^^^^^ expected `MethodRouter`, found `MethodRouter<AppState>`
    |                 |
    |                 arguments to this method are incorrect
    |
    = note: expected struct `MethodRouter<()>`
               found struct `MethodRouter<AppState>`
note: method defined here
   --> axum-0.8.9/src/routing/mod.rs:178:12
    |
178 |     pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self {
    |            ^^^^^
fn app(state: AppState) -> Router {
  Router::new()
    .with_state(state)
    .route("/", get(hello))
}
error[E0308]: mismatched types
   --> src/bin/st_order.rs:15:17
    |
 15 |     .route("/", get(hello))
    |      -----      ^^^^^^^^^^ expected `MethodRouter`, found `MethodRouter<AppState>`
    |      |
    |      arguments to this method are incorrect
    |
    = note: expected struct `MethodRouter<()>`
               found struct `MethodRouter<AppState>`

route 的参数是 MethodRouter<S>,S 是这个 Router 自己的类型参数。Router::new() 的 S 是推出来的:第一段里返回类型把它定成 ();第二段里 with_state(state) 把 Router::new() 的 S 定成 AppState,返回的 Router<S2> 又被返回类型定成 Router<()>,于是后面的 route 只收 MethodRouter<()>。with_state 写在前面时,状态被分发给了零个路由,然后就丢了。

4. 测试里直接 oneshot 一个 Router<AppState>。

use axum::{Router, body::Body, extract::State, http::Request, routing::get};
use tower::ServiceExt;

#[derive(Clone)]
struct AppState {
  name: String,
}

async fn hello(State(state): State<AppState>) -> String {
  format!("hello from {}", state.name)
}

fn api() -> Router<AppState> {
  Router::new().route("/hello", get(hello))
}

#[tokio::main]
async fn main() {
  let res = api().oneshot(Request::get("/hello").body(Body::empty()).unwrap()).await.unwrap();
  println!("{}", res.status());
}
error[E0599]: the method `oneshot` exists for struct `Router<AppState>`, but its trait bounds were not satisfied
  --> src/bin/st_test.rs:19:19
   |
19 |   let res = api().oneshot(Request::get("/hello").body(Body::empty()).unwrap()).await.unwrap();
   |                   ^^^^^^^
   |
  ::: axum-0.8.9/src/routing/mod.rs:68:1
   |
68 | pub struct Router<S = ()> {
   | ------------------------- doesn't satisfy `Router<AppState>: Service<_>` or `Router<AppState>: tower::ServiceExt<_>`
   |
   = note: the following trait bounds were not satisfied:
           `Router<AppState>: Service<_>`
           which is required by `Router<AppState>: tower::ServiceExt<_>`
           `&Router<AppState>: Service<_>`
           which is required by `&Router<AppState>: tower::ServiceExt<_>`
           `&mut Router<AppState>: Service<_>`
           which is required by `&mut Router<AppState>: tower::ServiceExt<_>`

Service<Request<B>> 只给了 Router<()>。测试里要先 .with_state(..),怎么在测试里构造 Router 见 不起服务器测路由。

报错位置真正的原因修法
expected Router, found Router<AppState>nest / merge 的参数、函数返回值子路由还欠状态,或者已经给了状态但返回类型写成了 Router<AppState>看子路由里有没有 with_state:没有就在父路由末尾统一 with_state;有就把返回类型改成 Router 或 Router<S>
expected MethodRouter, found MethodRouter<AppState>route 的参数这个 Router 被返回类型或前面的 with_state 定成了 Router<()>返回类型改成 Router<AppState>;with_state 挪到所有 route 之后
the method oneshot exists for struct Router<AppState>测试还欠状态,不是 Service测试里先 with_state
Router<AppState>: Service<IncomingStream<…>> is not satisfiedaxum::serve同上serve 之前 with_state,见 状态:State 与 FromRef
📝NOTE
冷知识:Router<S> 的文档第一句,是因为这个坑被问了太多次才加的

Router 结构体文档里,紧跟在第一句后面的「Router<S> means a router that is missing a state of type S to be able to handle requests. Thus, only Router<()> (i.e. without missing state) can be passed to serve.」来自 #3235「Add information about the meaning of Router<S> to Router documentation」(2025-02-27 合并,首次出现在 tag axum-v0.8.3)。PR 的动机是:「The meaning of S in Router<S> can be easily misunderstood, and the explanation is well-hidden in Router::with_state() documentation.」后面列了四个 discussion 和两个 issue。

那段「藏得很深」的解释本身也是为同一个坑写的:0.6.0 发布后不久的 #1580「Expand the docs for Router::with_state」,说明里写着「We've heard from a few users who have issues with getting the state types right, especially if they're returning a router (and thus have to name the type) and calling with_state.」它加进 with_state.md 的「Returning routers with states from functions」一节,正是上面第 1、2 种写法的正确版本;首次发布于 axum-v0.6.1。

修法

with_state.md 的建议可以归成两条:子路由不给状态,返回 Router<AppState>,在最外层统一 with_state;子路由必须自己给状态时,返回类型写成泛型 Router<S>,由 nest 的一方决定。两种写在一起:

axum = "=0.8.9"
tokio = { version = "=1.54.1", features = ["macros", "rt-multi-thread"] }
tower = { version = "=0.5.3", features = ["util"] }
http-body-util = "0.1"
use axum::{Router, body::Body, extract::State, http::Request, routing::get};
use http_body_util::BodyExt;
use tower::ServiceExt;

#[derive(Clone)]
struct AppState {
  name: String,
}

#[derive(Clone)]
struct AdminState {
  token: &'static str,
}

async fn hello(State(state): State<AppState>) -> String {
  format!("hello from {}", state.name)
}

async fn admin(State(state): State<AdminState>) -> String {
  format!("admin token {}", state.token)
}

// still missing AppState: say so in the type
fn api() -> Router<AppState> {
  Router::new().route("/hello", get(hello))
}

// AdminState is filled in here; the result can join any parent router
fn admin_routes<S>() -> Router<S> {
  Router::new()
    .route("/", get(admin))
    .with_state(AdminState { token: "t0p" })
}

fn app(state: AppState) -> Router {
  Router::new()
    .nest("/api", api())
    .nest("/admin", admin_routes())
    .with_state(state)
}

#[tokio::main]
async fn main() {
  let app = app(AppState { name: "demo".into() });
  for uri in ["/api/hello", "/admin"] {
    let res = app.clone().oneshot(Request::get(uri).body(Body::empty()).unwrap()).await.unwrap();
    let body = res.into_body().collect().await.unwrap().to_bytes();
    println!("{uri:<11} {}", String::from_utf8_lossy(&body));
  }
}

--release 连跑 3 次输出相同:

/api/hello  hello from demo
/admin      admin token t0p

admin_routes::<S>() 在 nest 处被推成 Router<AppState>:它自己的 AdminState 已经给了,接下来和父路由一起欠 AppState。返回类型写成泛型,admin_routes 就能挂进任何状态类型的父路由。子状态从总状态里取的另一种做法是 FromRef,见 状态:State 与 FromRef。

❗IMPORTANT
动脑筋:with_state 调两次,/a 看到的是哪个字符串?
use axum::{Router, body::Body, extract::State, http::Request, routing::get};
use http_body_util::BodyExt;
use tower::ServiceExt;

async fn show(State(s): State<String>) -> String {
  s
}

#[tokio::main]
async fn main() {
  let app: Router = Router::new()
    .route("/a", get(show))
    .with_state("first".to_owned())
    .route("/b", get(show))
    .with_state("second".to_owned());
  for uri in ["/a", "/b"] {
    let res = app.clone().oneshot(Request::get(uri).body(Body::empty()).unwrap()).await.unwrap();
    let body = res.into_body().collect().await.unwrap().to_bytes();
    println!("{uri}: {}", String::from_utf8_lossy(&body));
  }
}

能编译吗?能的话,/a 和 /b 各打印什么?答案在本页末尾。

动脑筋答案

能编译。M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:

/a: first
/b: second
  • 为什么能编译:第一个 with_state("first") 返回 Router<S2>,S2 由后面的 route("/b", get(show)) 推成 String,所以中间那个路由器的类型是「还欠一个 String」;第二个 with_state 再把它变成 Router<()>。
  • /a 为什么是 first:第一次 with_state 时,/a 的 handler 还是 BoxedHandler,被 MethodEndpoint::with_state 转成带着 "first" 的 Route。第二次 with_state 遇到的已经是 Route,原样保留(MethodEndpoint::Route(route) => MethodEndpoint::Route(route)),新的状态只装进了还没转换的 /b。

状态是在 with_state 那一刻按值装进每个端点的,不是请求时去查的;后给的状态不会覆盖先给的。

小结

  • Router<S> 的 S 是还欠的状态,只有 Router<()> 能 serve、能 oneshot;with_state<S2> 返回的类型由上下文选,不一定是 Router<()>。
  • 同一句「expected Router, found Router<AppState>」可能是忘了 with_state,也可能是给了状态却把返回类型写成了 Router<AppState>,修法相反。
  • 「expected MethodRouter, found MethodRouter<AppState>」说明这个 Router 已经被定成了 Router<()>:返回类型写成了 Router,或者 with_state 写在了 route 前面。
  • 子路由不给状态就返回 Router<AppState>,在最外层统一 with_state;自己给了状态就返回泛型 Router<S>。
  • 状态在 with_state 时装进端点,已经装好的端点不受之后的 with_state 影响。

💬 关联讨论

发起讨论
加载中...