讲解
泛型(generics)让代码对「多种类型」成立一次。函数 fn largest(list: &[T]) -> &T 里的 T 是类型参数:调用时由实参确定具体类型,一份代码服务所有满足条件的类型。你早已在用泛型——Option、Result<T, E>、Vec 都是泛型类型,本章学的是自己写。
性能上可以放心:Rust 的泛型靠单态化(monomorphization)实现——编译器为每个实际用到的具体类型生成一份专用代码,运行时开销为零。这和 Java 的类型擦除、动态语言的鸭子类型完全不同:泛型代码跑得和手写的具体类型版本一样快。
裸类型参数 T 在函数体里几乎什么都不能做——编译器不知道 T 能不能比较、能不能打印。要给 T 提要求,用特征约束(trait bound):fn largest<T: PartialOrd>(list: &[T]) -> &T 表示「T 必须可以比较大小」。约束是泛型代码的能力清单:标了什么就能用什么。下一章专门讲 trait 本身,本章先用起来。
示例
泛型函数 + 特征约束:
// T 必须可比较(PartialOrd),返回较大者的引用
fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
let mut result = list.first()?;
for item in &list[1..] {
if item > result {
result = item;
}
}
Some(result)
}
fn main() {
let nums = vec![34, 50, 25, 100, 65];
assert_eq!(largest(&nums), Some(&100));
let chars = vec!['y', 'm', 'a', 'q'];
assert_eq!(largest(&chars), Some(&'y'));
assert_eq!(largest::<i32>(&[]), None);
println!("数字和字符的最大值都找对了");
}
泛型结构体与 impl 块:
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
// impl 后面也要声明 <T>,编译器才知道 Point<T> 里的 T 是参数
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// 可以只为某个具体类型实现方法
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let int_point = Point { x: 5, y: 10 };
assert_eq!(int_point.x(), &5);
let float_point = Point { x: 3.0, y: 4.0 };
assert_eq!(float_point.distance_from_origin(), 5.0);
// int_point.distance_from_origin() 不存在:f64 专属方法
println!("{:?} 的 x 是 {}", int_point, int_point.x());
}
常见坑
- 以为泛型有运行时开销:单态化让每份实例化代码都是专用机器码,零成本;代价仅是编译产物略大。
- impl 漏写 :impl Point 里的 T 必须先在 impl 后声明,写成 impl Point 而漏掉 会报「找不到类型 T」。
- 对无约束的 T 提要求:T 没标 PartialOrd 就写 a > b,编译报错;报错信息会提示你缺哪个约束,照加即可。
- 过度泛型化:只为一种类型服务的函数写成泛型是负担;先写具体版本,出现第二个类型时再抽象。
小结
泛型 = 类型层面的参数化,单态化保证零开销;约束(T: PartialOrd)声明类型参数的能力;impl 泛型类型要写 impl。约束背后是 Rust 的核心抽象——下一章:Trait。