Rust in Everything
基于 axum 0.8.9

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。

📝NOTE
没有蠢问题

问: 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`
再加一个给双参数函数的 impl,其他不变,报错就变成:
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。

📝NOTE
冷知识:这句「must be the last argument」是 axum-macros 0.3.5 才有的

#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几个提取器合成一个元组参数
返回值没实现 IntoResponsehelp 行里的 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,会被检查函数的泛型参数遮住。

💬 关联讨论

发起讨论
加载中...