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 satisfied | axum::serve | 同上 | serve 之前 with_state,见 状态:State 与 FromRef |
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。
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, foundRouter<AppState>」可能是忘了with_state,也可能是给了状态却把返回类型写成了Router<AppState>,修法相反。 - 「expected
MethodRouter, foundMethodRouter<AppState>」说明这个Router已经被定成了Router<()>:返回类型写成了Router,或者with_state写在了route前面。 - 子路由不给状态就返回
Router<AppState>,在最外层统一with_state;自己给了状态就返回泛型Router<S>。 - 状态在
with_state时装进端点,已经装好的端点不受之后的with_state影响。