Table of contents
Open Table of contents
泛型解决什么问题
没有泛型时,同样的逻辑要为每个类型写一遍:
fn max_i32(a: i32, b: i32) -> i32 {
if a > b { a } else { b }
}
fn max_f64(a: f64, b: f64) -> f64 {
if a > b { a } else { b }
}
fn max_string(a: String, b: String) -> String {
if a > b { a } else { b }
}
// 逻辑完全一样,只是类型不同
泛型:把类型变成参数,写一次逻辑,适用于所有类型。
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
max(1, 2); // T = i32
max(3.14, 2.71); // T = f64
max("abc", "xyz"); // T = &str
1. 泛型函数
// T 是类型参数,调用时由编译器推断
fn first<T>(list: &[T]) -> &T {
&list[0]
}
let nums = vec![1, 2, 3];
let s = first(&nums); // T = i32
let names = vec!["Alice", "Bob"];
let s = first(&names); // T = &str
多个类型参数
fn zip_two<A, B>(a: A, b: B) -> (A, B) {
(a, b)
}
let pair = zip_two(42, "hello"); // A = i32, B = &str
trait bound:约束 T 能做什么
光写 T 没有任何约束——编译器不知道 T 有什么方法可以调用。
fn print_twice<T>(val: T) {
println!("{}", val); // 编译错误!T 不一定实现了 Display
}
// 加上 trait bound:T 必须实现 Display
fn print_twice<T: std::fmt::Display>(val: T) {
println!("{}", val); // OK
println!("{}", val);
}
// 多个约束用 +
fn process<T: Display + Debug + Clone>(val: T) { ... }
// 约束太长时用 where 子句(等价写法,更清晰)
fn process<T>(val: T)
where
T: Display + Debug + Clone,
{
...
}
2. 泛型结构体
// 一个可以装任何类型的容器
struct Container<T> {
value: T,
}
let c1 = Container { value: 42 }; // Container<i32>
let c2 = Container { value: "hello" }; // Container<&str>
let c3 = Container { value: vec![1, 2] }; // Container<Vec<i32>>
多个类型参数
struct Pair<A, B> {
first: A,
second: B,
}
let p = Pair { first: 1, second: "one" }; // Pair<i32, &str>
标准库中的泛型结构体
你一直在用的这些全是泛型结构体:
Vec<T> // 动态数组,T 是元素类型
HashMap<K, V> // 哈希表,K 是键类型,V 是值类型
Option<T> // 可能有值也可能没有
Result<T, E> // 成功时是 T,失败时是 E
Box<T> // 堆上的值
Arc<T> // 引用计数的共享指针
实际例子:游戏服务端
// API 的统一响应结构
struct ApiResponse<T> {
success: bool,
data: Option<T>,
error: Option<String>,
}
// T = Player
let resp: ApiResponse<Player> = ApiResponse {
success: true,
data: Some(player),
error: None,
};
// T = Vec<Item>
let resp: ApiResponse<Vec<Item>> = ApiResponse {
success: true,
data: Some(items),
error: None,
};
3. 泛型方法(impl 块上的泛型)
为泛型结构体实现方法
struct Container<T> {
value: T,
}
// 为所有 Container<T> 实现的方法(无约束)
impl<T> Container<T> {
fn new(value: T) -> Self {
Container { value }
}
fn into_inner(self) -> T {
self.value
}
fn as_ref(&self) -> &T {
&self.value
}
}
let c = Container::new(42);
let val = c.into_inner(); // val = 42
只对特定 T 实现的方法
// 只有 T: Display 时才有 show 方法
impl<T: std::fmt::Display> Container<T> {
fn show(&self) {
println!("Container holds: {}", self.value);
}
}
Container::new(42).show(); // OK,i32 实现了 Display
Container::new(vec![1]).show(); // 编译错误!Vec<i32> 没实现 Display
为特定具体类型实现的方法
// 只有 Container<f64> 才有 sqrt 方法
impl Container<f64> {
fn sqrt(&self) -> f64 {
self.value.sqrt()
}
}
Container::new(9.0).sqrt(); // OK
Container::new(9_i32).sqrt(); // 编译错误!i32 版本没有 sqrt 方法
方法自身也可以有泛型参数
impl<T> Container<T> {
// 方法的泛型参数 U,独立于结构体的 T
fn map<U, F>(self, f: F) -> Container<U>
where
F: FnOnce(T) -> U,
{
Container { value: f(self.value) }
}
}
let c = Container::new(42);
let c2 = c.map(|n| n.to_string()); // Container<i32> → Container<String>
4. 泛型 Trait
定义泛型 trait
// 可以把自己转换成某种类型 T
trait Into<T> {
fn into(self) -> T;
}
// 可以从某种类型 T 创建自己
trait From<T> {
fn from(value: T) -> Self;
}
这就是标准库中 From/Into 的定义。T 是”转换的目标/来源类型”。
实现泛型 trait
struct PlayerId(u32);
// PlayerId 可以从 u32 创建
impl From<u32> for PlayerId {
fn from(id: u32) -> Self {
PlayerId(id)
}
}
// PlayerId 可以从字符串解析
impl From<&str> for PlayerId {
fn from(s: &str) -> Self {
PlayerId(s.parse().unwrap())
}
}
let id1 = PlayerId::from(42_u32); // From<u32>
let id2 = PlayerId::from("123"); // From<&str>
关联类型 vs 泛型参数
trait 上的泛型有两种写法,含义不同:
// 泛型参数:一个类型可以实现多次(每个 T 一次)
trait From<T> {
fn from(val: T) -> Self;
}
// PlayerId 可以同时 impl From<u32> 和 impl From<&str>
// 因为 T 不同,是两个不同的 trait 实现
// 关联类型:一个类型只能实现一次(类型唯一确定)
trait Iterator {
type Item; // 关联类型
fn next(&mut self) -> Option<Self::Item>;
}
// Vec<i32> 的 Iterator::Item 只能是 i32,不可能同时是 String
// 因为"迭代出什么类型"对一个迭代器来说是确定的
选择规则:
- 同一个类型可能对多个 T 实现 → 泛型参数(如
From<T>) - 同一个类型只有一种实现 → 关联类型(如
Iterator::Item)
5. Trait Bound 的各种写法
// 写法 1:尖括号里
fn foo<T: Clone + Debug>(val: T) { ... }
// 写法 2:where 子句(推荐,复杂时更清晰)
fn foo<T>(val: T)
where
T: Clone + Debug,
{ ... }
// 写法 3:impl Trait(参数位置,语法糖)
fn foo(val: impl Clone + Debug) { ... }
// 三种写法等价(几乎,impl Trait 有细微差别)
返回值中的 impl Trait
// "返回某个实现了 Iterator 的类型,具体是什么调用者不需要知道"
fn make_counter() -> impl Iterator<Item = u32> {
0..100
}
// 不用 impl Trait 的话,你要写出具体类型名(可能巨长)
fn make_counter() -> std::ops::Range<u32> {
0..100
}
常见的 trait bound 组合
// 可以打印调试信息
T: Debug
// 可以克隆
T: Clone
// 可以比较大小
T: PartialOrd
// 可以序列化/反序列化(serde)
T: Serialize + DeserializeOwned
// 可以跨线程发送 + 共享
T: Send + Sync
// 可以作为 HashMap 的 key
T: Hash + Eq
// 可以在异步 task 中使用
T: Send + 'static
6. 泛型的编译:单态化(Monomorphization)
Rust 的泛型是零成本抽象——编译时展开,运行时没有额外开销。
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
max(1_i32, 2_i32); // 使用了 T = i32
max(3.14_f64, 2.71); // 使用了 T = f64
编译器看到两种具体类型被使用,生成两份具体代码:
// 编译器实际生成的(概念上)
fn max_i32(a: i32, b: i32) -> i32 {
if a > b { a } else { b }
}
fn max_f64(a: f64, b: f64) -> f64 {
if a > b { a } else { b }
}
好处:运行时直接调用具体函数,没有虚函数表查找,没有运行时类型判断。性能和手写每个类型一样。
代价:每种类型组合生成一份代码,编译产物可能更大、编译更慢。
对比:动态分发(dyn Trait)
// 静态分发(泛型):编译时确定类型,生成具体代码
fn process<T: Display>(val: T) { println!("{}", val); }
// 编译器知道 T 是什么,内联优化,零开销
// 动态分发(trait object):运行时通过虚表查找方法
fn process(val: &dyn Display) { println!("{}", val); }
// 编译器不知道具体类型,运行时查表调方法,有微小开销
// 但只生成一份代码,编译产物更小
什么时候用 dyn:
- 需要在集合中存储不同类型(
Vec<Box<dyn Animal>>) - 需要减少编译时间和二进制大小
- 类型只在运行时才知道
7. 实际例子:tower 的 Service 和 Layer
现在回头看 tower 的抽象,全是泛型:
// Service 对 Request 泛型
trait Service<Request> {
type Response;
type Error;
fn call(&mut self, req: Request) -> ...;
}
// Layer 对 S(内层 Service)泛型
trait Layer<S> {
type Service;
fn layer(&self, inner: S) -> Self::Service;
}
// TimeoutLayer 可以包装 **任何** Service
// 因为它对 S 完全泛型
impl<S> Layer<S> for TimeoutLayer {
type Service = TimeoutService<S>;
fn layer(&self, inner: S) -> TimeoutService<S> {
TimeoutService { inner, duration: self.duration }
}
}
// TimeoutService 对 S 和 Request 都泛型
impl<S, Request> Service<Request> for TimeoutService<S>
where
S: Service<Request>, // 内层必须也是 Service
{
type Response = S::Response; // 透传内层的响应类型
type Error = BoxError;
fn call(&mut self, req: Request) -> ... {
tokio::time::timeout(self.duration, self.inner.call(req))
}
}
这就是为什么同一个 TimeoutLayer 能同时用在 HTTP handler 和 gRPC handler 上——它对请求和响应类型完全泛型,只要内层是 Service 就行。
8. 总结
泛型函数: fn foo<T>(val: T) 把类型当参数传
泛型结构体: struct Foo<T> { val: T } 数据结构与类型无关
泛型方法: impl<T> Foo<T> { fn bar() } 方法与类型无关
泛型 trait: trait Foo<T> { fn bar(T) } 行为与类型无关
trait bound: T: Clone + Debug 约束 T 必须有哪些能力
编译时: 单态化 → 为每种具体类型生成专用代码 → 零运行时开销
运行时: 和手写每种类型一模一样,没有额外成本