Rust 学习笔记 14:泛型 (Generics)
“Abstraction without overhead.”
泛型是静态类型语言提高代码复用率的关键。
在 Go 中,我们习惯了 interface{} (或 any) 带来的运行时动态检查,或者复制粘贴代码。
Rust 的泛型则不同,它在编译期通过单态化 (Monomorphization) 展开代码,运行时没有性能损耗。
1. 泛型函数
假设我们要写一个找最大值的函数,既能找 i32 也能找 char。
1fn get_largest<T: PartialOrd + Copy>(list: &[T]) -> T {
2 let mut largest = list[0];
3 for &item in list {
4 if item > largest { // 需要 T 实现 PartialOrd
5 largest = item;
6 }
7 }
8 largest
9}
这里 T 是类型参数。<T: PartialOrd + Copy> 叫做 Trait Bound(类似 Java 的 T extends ... 或 Go 的 T interface ...)。它告诉编译器:这个 T 不是任意类型,它必须能比较大小,而且能被 Copy(为了简单起见,这里假设是栈上数据)。
2. 泛型结构体与枚举
我们之前用过的 Option<T> 和 Result<T, E> 就是典型的泛型枚举。
自定义泛型结构体也很简单:
1struct Point<T> {
2 x: T,
3 y: T,
4}
注意:这里 x 和 y 必须是同一种类型 T。
如果是 Point { x: 5, y: 4.0 },编译器会报错。如果想不同,得定义 struct Point<T, U>.
3. 泛型方法
在 impl 块中使用泛型:
1impl<T> Point<T> {
2 fn x(&self) -> &T {
3 &self.x
4 }
5}
如果你只想给特定类型实现方法:
1impl Point<f32> {
2 fn distance(&self) -> f32 { ... }
3}
这样只有 Point<f32> 才有 distance 方法,Point<i32> 没有。
4. 单态化 (Monomorphization)
Rust 的泛型性能极高,因为编译器会在编译时将泛型代码展开成具体类型的代码。
例如,如果你用了 Point<i32> 和 Point<f64>,编译器会生成两个不同的结构体(类似 Point_i32 和 Point_f64),并把代码中的泛型调用替换成具体的函数调用。
这意味着:
- 没有运行时开销(不需要像 Java 那样装箱/拆箱,也不需要运行时查虚表)。
- 二进制体积会膨胀(同样的逻辑生成了多份机器码)。
5. 小结
第十四篇笔记。
- 泛型让我们可以写出通用代码,避免重复。
- Trait Bounds 用来限制泛型必须具备的行为(下一章详解)。
- 单态化 保证了泛型的执行效率。
下一篇,我们将深入讲解 Traits。泛型定义了"参数化类型",而 Traits 定义了"类型的行为约定",两者结合才是 Rust 面向接口编程的完全体。
练习题:
- 定义一个泛型结构体
Stack<T>,实现push和pop方法。 - 尝试编写一个函数,接受两个不同类型的点
Point<T>和Point<U>,返回一个混合类型的点PointMixed<T, U>。
思考题:
Java 的泛型是类型擦除 (Type Erasure) 的,而 Rust 是单态化的。这两种实现方式对性能和运行时行为有什么影响?
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
