讲解

泛型(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。