Table of contents
Open Table of contents
第一步:一个普通 trait + 一个普通结构体
trait Greet {
fn hello(&self) -> String;
}
struct Dog {
name: String,
}
impl Greet for Dog {
fn hello(&self) -> String {
format!("Woof! I'm {}", self.name)
}
}
let dog = Dog { name: "Rex".into() };
dog.hello(); // "Woof! I'm Rex"
没有泛型,一切都是具体类型。
第二步:结构体加上泛型
struct Container<T> {
value: T,
}
Container<T> 不是一个类型——它是一个模具。给它一个具体类型,才能压出一个具体类型:
Container<i32> → 一个装 i32 的容器(具体类型)
Container<String> → 一个装 String 的容器(另一个具体类型)
Container<Dog> → 一个装 Dog 的容器(又一个具体类型)
它们之间没有关系,就像”铁盒子”和”纸盒子”都是盒子但是不同的东西。
第三步:为泛型结构体实现普通 trait
impl<T: Display> Greet for Container<T> {
fn hello(&self) -> String {
format!("I'm a container holding: {}", self.value)
}
}
Container { value: 42 }.hello(); // OK,i32 能 Display
Container { value: "hi" }.hello(); // OK,&str 能 Display
Container { value: vec![1] }.hello(); // 编译错误!Vec 不能 Display
impl<T: Display> 读作:“对于任何实现了 Display 的类型 T”。
第四步:Trait 也加上泛型
trait Extract<S> {
fn extract(source: &S) -> Self;
}
Extract<S> 也是一个模具。S 不同,trait 就不同:
Extract<HttpRequest> → "我能从 HTTP 请求中提取出来"
Extract<String> → "我能从字符串中提取出来"
Extract<Config> → "我能从配置中提取出来"
第五步:为具体结构体实现泛型 trait
struct UserId(u32);
// UserId 能从 HttpRequest 中提取
impl Extract<HttpRequest> for UserId {
fn extract(source: &HttpRequest) -> Self {
let id: u32 = source.path_param("id").parse().unwrap();
UserId(id)
}
}
// UserId 也能从 Config 中提取
impl Extract<Config> for UserId {
fn extract(source: &Config) -> Self {
UserId(source.default_user_id)
}
}
同一个 UserId,对不同的 S 有不同的提取逻辑。这就是泛型 trait 的意义——同一个行为,针对不同的上下文有不同的实现。
第六步:泛型结构体 + 泛型 trait
struct Wrapper<T> {
value: T,
}
trait Extract<S> {
fn extract(source: &S) -> Self;
}
impl<T: FromStr> Extract<HttpRequest> for Wrapper<T> {
fn extract(source: &HttpRequest) -> Self {
let raw: &str = source.body_str();
let value: T = raw.parse().unwrap();
Wrapper { value }
}
}
读法:“对于任何能从字符串解析的类型 T,Wrapper<T> 都能从 HttpRequest 中提取”。
Wrapper<u32> 能从 HttpRequest 提取 → 解析 body 为 u32
Wrapper<f64> 能从 HttpRequest 提取 → 解析 body 为 f64
Wrapper<String> 能从 HttpRequest 提取 → 解析 body 为 String
这就是 axum 的 Json<T> 的原理。
第七步:回看 axum
// axum 的 trait(简化)
trait FromRequest<S> {
fn from_request(req: Request, state: &S) -> Result<Self, Error>;
}
// axum 的 Json(简化)
struct Json<T>(pub T);
impl<S, T: DeserializeOwned> FromRequest<S> for Json<T> {
fn from_request(req: Request, _state: &S) -> Result<Self, Error> {
let bytes = read_body(req);
let value: T = serde_json::from_slice(&bytes)?;
Ok(Json(value))
}
}
把泛型”实例化”来看:
Json<Player> 的 from_request → 读 body → 反序列化为 Player → 返回 Json(player)
Json<Item> 的 from_request → 读 body → 反序列化为 Item → 返回 Json(item)
Json<String> 的 from_request → 读 body → 反序列化为 String → 返回 Json(string)
你写 Json<Player> 时,编译器把 T 替换成 Player,生成一份专用代码。
第八步:泛型 trait 参数不约束 = 对所有类型成立
axum 的 FromRequest<S> 中 S 是应用状态的类型。不同 extractor 对 S 的态度不同:
// Json:不关心 S 是什么(解析 body 跟应用状态无关)
impl<S, T: DeserializeOwned> FromRequest<S> for Json<T> { ... }
// ↑
// 不约束 S → "不管你 state 是什么类型,我都能从 body 提取"
// Path:也不关心 S(从 URL 提取跟应用状态无关)
impl<S, T: DeserializeOwned> FromRequestParts<S> for Path<T> { ... }
// ↑
// 不约束 S → "不管你 state 是什么类型,我都能从 URL 提取"
// State:必须知道 S 是什么(因为它要返回 S 本身)
impl<S: Clone> FromRequestParts<S> for State<S> { ... }
// ↑ ↑
// 约束 S: Clone State 提取出来的就是 S 本身
对比表:
Extractor 对 S 的态度 原因
──────────────────────────────────────────────────
Json<T> impl<S> 解析 body 不需要 state
Path<T> impl<S> 解析 URL 不需要 state
Query<T> impl<S> 解析查询参数不需要 state
HeaderMap impl<S> 读请求头不需要 state
State<S> impl<S: Clone> 它返回的就是 state 本身,必须能 clone
不约束 = 对所有 S 都成立 = 不管你 with_state() 传了什么类型都能用。