the trait bound fn(...) {handler}: Handler<_, _> is not satisfied
主线位置:装配主线的第一步。
get(handler)要求函数实现Handler<T, S>,宏生成的 impl 有五条约束,哪一条不满足,这一步就编译不过;请求主线还没开始。
flowchart TB fnitem["async fn handler"] --> getnode["get(handler)"] getnode --> want["要求 Handler T S"] want --> c1["future 是 Send"] want --> c2["前面参数 Parts"] want --> c3["最后一个 Request"] want --> c4["返回值 IntoResponse"] want --> c5["参数不超过 16 个"] c1 -->|任何一条不成立| err["Handler 不满足"] c2 --> err c3 --> err c4 --> err c5 --> err err --> dbg["debug_handler"]
Handler 的宏实现在 Handler trait 一页逐行讲过。和这个报错有关的只是 impl_handler! 的 where 子句:
impl<F, Fut, S, Res, M, $($ty,)* $last> Handler<(M, $($ty,)* $last,), S> for F
where
F: FnOnce($($ty,)* $last,) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
S: Send + Sync + 'static,
Res: IntoResponse,
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S, M> + Send,
| 约束 | 不满足时的典型写法 |
|---|---|
Fut: ... + Send | 跨 .await 持有 std::sync::MutexGuard 或 Rc |
$ty: FromRequestParts<S> | 把 Json、String 这类读 body 的提取器放在了前面 |
$last: FromRequest<S, M> | 参数类型根本不是提取器,或者 Json<T> 的 T 没实现 Deserialize |
Res: IntoResponse | 返回 Result<_, E>,而 E 没实现 IntoResponse |
宏只展开到 T16 | 第 17 个参数没有 impl 可选 |
五种情况得到的是同一句报错。下面逐个复现,每个先看不加属性的报错,再加上 #[axum::debug_handler] 用 debug 构建看它说什么。所有程序都在 M2 Max、rustc 1.99.0 上编译,依赖:
axum = { version = "=0.8.9", features = ["macros"] }
serde = { version = "1", features = ["derive"] }
macros 只有 debug_handler 需要。不加属性的版本用 --release 编译,加属性的版本用 debug 编译:debug_handler 在 #[cfg(not(debug_assertions))] 下原样返回输入,release 里什么也不检查(见 FromRequestParts 与 FromRequest)。报错里 registry 的路径缩短成了 axum-0.8.9/...。
1. future 不是 Send
最常见的一种:状态里放了 Arc<Mutex<_>>,拿到 guard 之后又 .await 了别的东西。
use std::sync::{Arc, Mutex};
use axum::{Router, extract::State, routing::post};
#[derive(Clone, Default)]
struct AppState {
hits: Arc<Mutex<u64>>,
}
async fn audit(n: u64) {
let _ = n;
}
async fn hit(State(state): State<AppState>) -> String {
let mut hits = state.hits.lock().unwrap();
*hits += 1;
audit(*hits).await;
format!("hits = {hits}")
}
fn main() {
let _app: Router = Router::new()
.route("/hit", post(hit))
.with_state(AppState::default());
}
完整的报错:
error[E0277]: the trait bound `fn(State<AppState>) -> impl Future<Output = String> {hit}: Handler<_, _>` is not satisfied
--> src/bin/send.rs:23:25
|
23 | .route("/hit", post(hit))
| ---- ^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(State<AppState>) -> impl Future<Output = String> {hit}`
| |
| required by a bound introduced by this call
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
note: required by a bound in `post`
--> axum-0.8.9/src/routing/method_routing.rs:167:16
|
167 | H: Handler<T, S>,
| ^^^^^^^^^^^^^ required by this bound in `post`
...
445 | top_level_handler_fn!(post, POST);
| ---------------------------------
| | |
| | required by a bound in this function
| in this macro invocation
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
报错里没有 Send,没有 MutexGuard,也没有指向第 16 行。它只说了三件事:哪个函数({hit})、在哪一行被要求成为 handler、H: Handler<T, S> 这条约束来自 top_level_handler_fn! 生成的 post。后面四种情况的报错,从 = note: Consider using 往下都和这里一字不差,下面只引用前几行。
在 async fn hit 上面加一行 #[axum::debug_handler],debug 构建的第一条错误:
error: future cannot be sent between threads safely
--> src/bin/send_dbg.rs:14:1
|
14 | #[axum::debug_handler]
| ^^^^^^^^^^^^^^^^^^^^^^ future returned by `hit` is not `Send`
|
= help: within `impl Future<Output = String>`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, u64>`
note: future is not `Send` as this value is used across an await
--> src/bin/send_dbg.rs:18:16
|
16 | let mut hits = state.hits.lock().unwrap();
| -------- has type `std::sync::MutexGuard<'_, u64>` which is not `Send`
17 | *hits += 1;
18 | audit(*hits).await;
| ^^^^^ await occurs here, with `mut hits` maybe used later
note: required by a bound in `__axum_macros_check_hit_future::check`
--> src/bin/send_dbg.rs:14:1
|
14 | #[axum::debug_handler]
| ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `check`
= note: this error originates in the attribute macro `axum::debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)
第二条错误还是上面那条 Handler<_, _>:debug_handler 只是在旁边多生成几个检查函数,原来的约束照样报。换成 Rc 也一样,不加属性时只有 fn() -> impl Future<Output = String> {page}: Handler<_, _>,加了之后:
use std::rc::Rc;
use axum::{Router, routing::get};
async fn load() -> u32 {
7
}
#[axum::debug_handler]
async fn page() -> String {
let title = Rc::new(String::from("home"));
let n = load().await;
format!("{title}: {n}")
}
fn main() {
let _app: Router = Router::new().route("/", get(page));
}
error: future cannot be sent between threads safely
--> src/bin/send_rc_dbg.rs:9:1
|
9 | #[axum::debug_handler]
| ^^^^^^^^^^^^^^^^^^^^^^ future returned by `page` is not `Send`
|
= help: within `impl Future<Output = String>`, the trait `Send` is not implemented for `Rc<String>`
note: future is not `Send` as this value is used across an await
--> src/bin/send_rc_dbg.rs:12:18
|
11 | let title = Rc::new(String::from("home"));
| ----- has type `Rc<String>` which is not `Send`
12 | let n = load().await;
| ^^^^^ await occurs here, with `title` maybe used later
这条信息和 tokio::spawn 一个非 Send future 时的报错是同一种,原因和三种修法(用块让 guard 提前结束、换 tokio::sync::Mutex、spawn_local)在 tokio 卷的 future cannot be sent between threads safely 里;那一页也验证了 rustc 1.99.0 上 drop(guard) 之后再 .await 仍然报错,要用块。axum 这边没有 spawn_local 那条路:Fut: Send 写死在 Handler 的 impl 里,Handler::Future 本身也要求 Send。
问: rustc 明明知道 future 不是 Send,加了 debug_handler 就能说出来,为什么不加的时候一个字都不提?
答: 因为它不知道你想用哪个 impl。报错里写的是 Handler<_, _>,两个类型参数都没推出来,而 Handler 有 20 个 impl,编译器没法挑一个出来解释「这个 impl 的哪条约束不满足」。用一个不依赖 axum 的最小版本验证:一个 Handler<T> trait、一个给单参数函数的 impl,约束里要求 Fut: Send,用一个持有 Rc 跨 .await 的函数去调 get(page)。只有这一个 impl 时,rustc 1.99.0 直接报:
error: future cannot be sent between threads safely
--> src/bin/dnr_off.rs:24:7
|
24 | get(page);
| ^^^^ future returned by `page` is not `Send`
error[E0277]: the trait bound `fn(u32) -> impl Future<Output = String> {page}: Handler<_>` is not satisfied
只有一个 impl、但在它上面加 #[diagnostic::do_not_recommend],报错也不再提 Send,只剩「Handler<(u32,)> is not implemented」;axum 0.8.9 的每个 Handler impl 都带着这个属性(来历见 Handler trait 的冷知识)。debug_handler 的办法是绕开这个问题:它为每一条约束单独生成一个只有一个 impl 可选的检查函数,编译器就能把原因说具体。
2. body 提取器不在最后
use axum::{Json, Router, http::HeaderMap, routing::post};
use serde::Deserialize;
#[derive(Deserialize)]
struct NewUser {
name: String,
}
async fn create(Json(user): Json<NewUser>, headers: HeaderMap) -> String {
format!("{} from {:?}", user.name, headers.get("user-agent"))
}
fn main() {
let _app: Router = Router::new().route("/users", post(create));
}
error[E0277]: the trait bound `fn(Json<NewUser>, HeaderMap) -> impl Future<Output = String> {create}: Handler<_, _>` is not satisfied
--> src/bin/order.rs:14:57
|
14 | let _app: Router = Router::new().route("/users", post(create));
| ---- ^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Json<NewUser>, HeaderMap) -> impl Future<Output = String> {create}`
加 debug_handler:
error: `Json<_>` consumes the request body and thus must be the last argument to the handler function
--> src/bin/order_dbg.rs:10:29
|
10 | async fn create(Json(user): Json<NewUser>, headers: HeaderMap) -> String {
| ^^^^
这条不是类型检查得出的,是 check_input_order 按类型的名字查 request_consuming_type_name 那张表(Json、Form、Bytes、String、Request、Multipart 等)生成的 compile_error!。一旦这一步报了错,宏就不再生成逐个参数的 trait 检查,所以不会再多一条 Json<NewUser>: FromRequestParts 不满足。表里没有的类型(比如你自己写的 body 提取器)放错位置,得到的是第 5 种情况那样的 trait 报错。为什么 body 只能给最后一个参数,见 FromRequestParts 与 FromRequest。
#1797「Add special handling of FromRequest extractors not being the last arg」的说明只有一句:「In the case a known FromRequest extractor isn't the last argument #[debug_handler] will now give a more precise error.」axum-macros 的 CHANGELOG 记在 0.3.5(2023-03-03)下:「In #[debug_handler] provide specific errors about FromRequest extractors not being the last argument」。
在这之前,debug_handler 对每个非最后参数一律生成 FromRequestParts 检查。这个 PR 删掉的 trybuild 期望输出 doesnt_implement_from_request_parts.stderr 记下了当时的样子:对 async fn handler(_: String, _: Method) 报的是「the trait bound String: FromRequestParts<()> is not satisfied」,候选列表里是 <() as FromRequestParts<S>>、<(T1, T2) as FromRequestParts<S>> 一直到八元组。同一个 PR 新加的 wrong_order.stderr 就是今天这句话,还加了两个 body 提取器同时出现时的「Can't have two extractors that consume the request body」。提交 416a0568 第一次出现在 tag axum-macros-v0.3.5。
3. 超过 16 个参数
use axum::{Router, http::HeaderMap, routing::get};
type H = HeaderMap;
async fn seventeen(
_: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H,
_: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H,
) {}
fn main() {
let _app: Router = Router::new().route("/17", get(seventeen));
}
error[E0277]: the trait bound `fn(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) -> _ {seventeen}: Handler<_, _>` is not satisfied
--> src/bin/seventeen.rs:11:53
|
11 | let _app: Router = Router::new().route("/17", get(seventeen));
| --- ^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) -> _ {seventeen}`
类型名太长,rustc 把参数都写成了 _,完整类型写进了一个文件(报错末尾有一条 note 给出路径,这里省略)。数一数 _ 是 17 个,这就是全部线索。加 debug_handler:
error: Handlers cannot take more than 16 arguments. Use `(a, b): (ExtractorA, ExtractorA)` to further nest extractors
--> src/bin/seventeen_dbg.rs:7:3
|
7 | / _: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H,
8 | | _: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H,
| |_______________________________________________________^
check_extractor_count 里写死了 let max_extractors = 16;,与 all_the_tuples! 展开的上限一致。
这里的类型别名叫 H 不是随手起的。第一次写这个例子时别名叫 M,debug_handler 多报了一条莫名其妙的 error[E0283]: type annotations needed ... cannot infer type of the type parameter M:它给最后一个参数生成的检查函数是 fn ...<M>() where #ty: FromRequest<(), M>(check_inputs_impls_from_request),参数类型 M 被检查函数自己的泛型参数 M 遮住了,约束变成了 M: FromRequest<(), M>。自己的类型别名避开 M 这个名字就不会碰到。
4. 返回值没实现 IntoResponse
use axum::{Router, extract::Path, routing::get};
#[derive(Debug)]
enum AppError {
NotFound,
}
async fn user(Path(id): Path<u32>) -> Result<String, AppError> {
if id == 0 {
return Err(AppError::NotFound);
}
Ok(format!("user {id}"))
}
fn main() {
let _app: Router = Router::new().route("/users/{id}", get(user));
}
error[E0277]: the trait bound `fn(Path<u32>) -> impl Future<Output = _> {user}: Handler<_, _>` is not satisfied
--> src/bin/ret.rs:16:61
|
16 | let _app: Router = Router::new().route("/users/{id}", get(user));
| --- ^^^^ unsatisfied trait bound
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<_, _>` is not implemented for fn item `fn(axum::extract::Path<u32>) -> impl Future<Output = Result<String, AppError>> {user}`
注意第一行里输出类型被缩成了 Output = _,要到 help 那一行才看得到 Result<String, AppError>。加 debug_handler(末尾一条长类型名的 note 省略):
error[E0277]: the trait bound `AppError: IntoResponse` is not satisfied
--> src/bin/ret_dbg.rs:9:39
|
9 | async fn user(Path(id): Path<u32>) -> Result<String, AppError> {
| ^^^^^^ unsatisfied trait bound
|
help: the trait `IntoResponse` is not implemented for `AppError`
--> src/bin/ret_dbg.rs:4:1
|
4 | enum AppError {
| ^^^^^^^^^^^^^
= help: the following other types implement trait `IntoResponse`:
()
(R,)
(Response<()>, R)
(StatusCode, R)
(T1, R)
(axum::http::response::Parts, R)
(Response<()>, T1, R)
(StatusCode, T1, R)
and 132 others
= note: required for `Result<String, AppError>` to implement `IntoResponse`
note: required by a bound in `__axum_macros_check_user_into_response::{closure#0}::check`
--> src/bin/ret_dbg.rs:9:39
|
9 | async fn user(Path(id): Path<u32>) -> Result<String, AppError> {
| ^^^^^^ required by this bound in `check`
check_output_impls_into_response 把函数体抄进一个返回同样类型的 async fn,再把结果交给 fn check<T: IntoResponse>(_: T),所以这条错误能一直追到 AppError。候选列表里排在前面的还是元组:IntoResponse 的 impl 没有加 do_not_recommend。修法是给错误类型实现 IntoResponse,惯用写法见 IntoResponse 与 IntoResponseParts。
5. 参数不是提取器
两种常见写法。一种是 Json<T> 的 T 忘了 #[derive(Deserialize)]:
use axum::{Json, Router, routing::post};
struct NewUser {
name: String,
}
async fn create(Json(user): Json<NewUser>) -> String {
format!("created {}", user.name)
}
fn main() {
let _app: Router = Router::new().route("/users", post(create));
}
error[E0277]: the trait bound `fn(Json<NewUser>) -> impl Future<Output = String> {create}: Handler<_, _>` is not satisfied
--> src/bin/extractor.rs:12:57
|
12 | let _app: Router = Router::new().route("/users", post(create));
| ---- ^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Json<NewUser>) -> impl Future<Output = String> {create}`
加 debug_handler,第一条(同样的错误紧接着又报了一遍,第二遍多一条 required by a bound in __axum_macros_check_create_0_from_request_check,省略):
error[E0277]: the trait bound `NewUser: serde::Deserialize<'de>` is not satisfied
--> src/bin/extractor_dbg.rs:8:29
|
8 | async fn create(Json(user): Json<NewUser>) -> String {
| ^^^^ unsatisfied trait bound
|
help: the trait `for<'de> serde_core::de::Deserialize<'de>` is not implemented for `NewUser`
--> src/bin/extractor_dbg.rs:3:1
|
3 | struct NewUser {
| ^^^^^^^^^^^^^^
= note: for local types consider adding `#[derive(serde::Deserialize)]` to your `NewUser` type
= note: for types from other crates check whether the crate offers a `serde` feature flag
= help: the following other types implement trait `serde_core::de::Deserialize<'de>`:
&'a [u8]
&'a serde_json::raw::RawValue
&'a std::path::Path
&'a str
()
(T,)
(T0, T1)
(T0, T1, T2)
and 143 others
= note: required for `NewUser` to implement `serde_core::de::DeserializeOwned`
= note: required for `Json<NewUser>` to implement `FromRequest<()>`
另一种是直接拿结构体当参数,忘了包 Json:async fn create(user: NewUser) -> String(NewUser 有 Deserialize)。不加属性时还是 fn(NewUser) -> impl Future<Output = String> {create}: Handler<_, _>,加了之后:
error[E0277]: the trait bound `NewUser: FromRequest<(), axum_core::extract::private::ViaParts>` is not satisfied
--> src/bin/bare_dbg.rs:10:23
|
10 | async fn create(user: NewUser) -> String {
| ^^^^^^^ unsatisfied trait bound
|
help: the trait `FromRequestParts<()>` is not implemented for `NewUser`
--> src/bin/bare_dbg.rs:5:1
|
5 | struct NewUser {
| ^^^^^^^^^^^^^^
= note: Function argument is not a valid axum extractor.
See `https://docs.rs/axum/0.8/axum/extract/index.html` for details
= help: the following other types implement trait `FromRequestParts<S>`:
`ConnectInfo<T>` implements `FromRequestParts<S>`
`Extension<T>` implements `FromRequestParts<S>`
`HeaderMap` implements `FromRequestParts<S>`
`MatchedPath` implements `FromRequestParts<S>`
`Method` implements `FromRequestParts<S>`
`OriginalUri` implements `FromRequestParts<S>`
`Query<T>` implements `FromRequestParts<S>`
`RawPathParams` implements `FromRequestParts<S>`
and 7 others
= note: required for `NewUser` to implement `FromRequest<(), axum_core::extract::private::ViaParts>`
「Function argument is not a valid axum extractor.」是 FromRequestParts / FromRequest 上 on_unimplemented 的 note。编译器挑了 ViaParts 那条 blanket impl 来解释(FromRequestParts 与 FromRequest 讲了这两个标记),于是说的是「没实现 FromRequestParts」;对这个类型真正要做的是包一层 Json。Option<Query<T>> 这种「看起来是提取器、其实没有实现」的情况单独成章:Option<Query<T>> 编译不过。
debug_handler 做了什么
debug_handler 在 debug 构建里调 expand,原函数原样保留,旁边追加几组隐藏的检查:
quote! {
#item_fn
#check_extractor_count
#check_path_extractor
#check_output_impls_into_response
#check_inputs_and_future_send
#middleware_takes_next_as_last_arg
}
| 检查 | 生成什么 | 对应上面的情况 |
|---|---|---|
check_extractor_count | 参数超过 16 个就 compile_error! | 3 |
check_output_impls_into_response | 把函数体抄进一个 async fn,结果交给 check<T: IntoResponse> | 4 |
check_input_order | 按名字查表,读 body 的类型不在最后就 compile_error! | 2 |
check_inputs_impls_from_request | 每个参数一个检查函数:前面的要 FromRequestParts<S>,最后一个要 FromRequest<S, M> | 5 |
check_future_send | 用 panic!() 充当所有参数调用一次函数,把 future 交给 check<T: Future + Send> | 1 |
check_input_order 报了错就不再生成 check_inputs_impls_from_request,两者只出一个。状态类型 S 从参数里的 State<..> 取;参数里没有 State 时按 () 检查,有两种以上时报「can't infer state type」,要写 #[debug_handler(state = AppState)]。函数带泛型参数时,参数检查和 Send 检查都跳过,直接报「#[axum_macros::debug_handler] doesn't support generic functions」。
对照表
| 原因 | 不加属性时报错里能看到的 | debug_handler 说的 | 修法 |
|---|---|---|---|
跨 .await 持有 MutexGuard / Rc | 什么也看不到 | future cannot be sent between threads safely,指出变量和 .await 所在行 | 用块让它在 .await 前结束;或换 tokio::sync::Mutex |
| body 提取器不在最后 | 参数列表 fn(Json<NewUser>, HeaderMap) | `Json<_>` consumes the request body and thus must be the last argument | 调换顺序;只能有一个读 body 的提取器 |
| 超过 16 个参数 | fn(_, _, ...),数得出 17 个 _ | Handlers cannot take more than 16 arguments | 几个提取器合成一个元组参数 |
返回值没实现 IntoResponse | help 行里的 Output = Result<String, AppError> | `AppError: IntoResponse` is not satisfied | 给错误类型实现 IntoResponse |
| 参数不是提取器 | 参数类型本身 | NewUser: Deserialize 或 NewUser: FromRequestParts 不满足,附「not a valid axum extractor」 | 补 derive(Deserialize),或包上正确的提取器 |
前四列里只有「参数类型」和「返回类型」是不加属性也能看出来的;碰到这个报错,最省事的是先给函数加 #[axum::debug_handler],用 debug 构建跑一次 cargo check。它在 release 下不生成任何代码,可以一直留着。
修好以后
把五个修法放进同一个程序,用 oneshot 各调一次:
axum = "=0.8.9"
serde = { version = "1", features = ["derive"] }
tokio = { version = "=1.54.1", features = ["macros", "rt-multi-thread"] }
tower = { version = "=0.5.3", features = ["util"] }
http-body-util = "0.1"
use std::sync::{Arc, Mutex};
use axum::{
Json, Router,
body::Body,
extract::{Path, State},
http::{HeaderMap, Request, StatusCode},
response::{IntoResponse, Response},
routing::{get, post},
};
use http_body_util::BodyExt;
use serde::Deserialize;
use tower::ServiceExt;
#[derive(Clone, Default)]
struct AppState {
hits: Arc<Mutex<u64>>,
}
async fn audit(n: u64) {
let _ = n;
}
// 1. the guard lives in a block that ends before `.await`
async fn hit(State(state): State<AppState>) -> String {
let n = {
let mut hits = state.hits.lock().unwrap();
*hits += 1;
*hits
};
audit(n).await;
format!("hits = {n}")
}
#[derive(Deserialize)]
struct NewUser {
name: String,
}
// 2. the body extractor moves to the end; 5. NewUser derives Deserialize
async fn create(headers: HeaderMap, Json(user): Json<NewUser>) -> String {
format!("{} from {:?}", user.name, headers.get("user-agent"))
}
// 3. seventeen extractors, packed into one tuple argument
type H = HeaderMap;
async fn seventeen(
_: H, _: H, _: H, _: H, _: H, _: H, _: H, _: H,
_: H, _: H, _: H, _: H, _: H, _: H, _: H, (_, _): (H, H),
) -> &'static str {
"17 extractors"
}
// 4. the error type implements IntoResponse
#[derive(Debug)]
enum AppError {
NotFound,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
match self {
AppError::NotFound => (StatusCode::NOT_FOUND, "no such user").into_response(),
}
}
}
async fn user(Path(id): Path<u32>) -> Result<String, AppError> {
if id == 0 {
return Err(AppError::NotFound);
}
Ok(format!("user {id}"))
}
#[tokio::main]
async fn main() {
let app: Router = Router::new()
.route("/hit", post(hit))
.route("/users", post(create))
.route("/17", get(seventeen))
.route("/users/{id}", get(user))
.with_state(AppState::default());
let reqs = [
Request::post("/hit").body(Body::empty()).unwrap(),
Request::post("/hit").body(Body::empty()).unwrap(),
Request::post("/users")
.header("content-type", "application/json")
.header("user-agent", "curl/8")
.body(Body::from(r#"{"name":"ann"}"#))
.unwrap(),
Request::get("/17").body(Body::empty()).unwrap(),
Request::get("/users/7").body(Body::empty()).unwrap(),
Request::get("/users/0").body(Body::empty()).unwrap(),
];
for req in reqs {
let line = format!("{} {}", req.method(), req.uri());
let res = app.clone().oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
println!("{line:<16} {status} {}", String::from_utf8_lossy(&body));
}
}
--release 连跑 3 次,输出相同:
POST /hit 200 OK hits = 1
POST /hit 200 OK hits = 2
POST /users 200 OK ann from Some("curl/8")
GET /17 200 OK 17 extractors
GET /users/7 200 OK user 7
GET /users/0 404 Not Found no such user
第一种情况换成 tokio::sync::Mutex 也能编译:把 lock().unwrap() 改成 lock().await,原来的写法不动,guard 跨 .await 也没关系。两种修法怎么选,见 tokio 卷的 tokio 的 Mutex 还是 std 的 Mutex。
小结
- 「
Handler<_, _>is not satisfied」只说明宏生成的 impl 有一条约束不成立,不说是哪条;Handler有 20 个 impl,编译器不会替你挑一个解释。 - 五种常见原因:future 不是
Send、读 body 的提取器不在最后、超过 16 个参数、返回值没实现IntoResponse、参数类型不是提取器。不加属性时,非Send这一种在报错里完全看不出来。 #[axum::debug_handler]在 debug 构建里为每条约束单独生成检查,报出具体原因;release 构建里它原样返回函数,可以常驻。- 「must be the last argument」那条按类型名字查表,自定义的 body 提取器不在表里;参数类型别名别叫
M,会被检查函数的泛型参数遮住。