Rust 泛型从“复制粘贴”到“万能神器”
上一节我们认识了泛型,这一节我们要深入它的世界,看看它如何在函数、结构体、枚举和方法中大显身手!”
第一关:函数泛型——消灭“孪生函数”
还记得这两个长得一模一样的函数吗?
fn largest_i32(list: &[i32]) -> &i32 { ... }
fn largest_char(list: &[char]) -> &char { ... }
它们就像一对“双胞胎”,除了处理的类型不同,其他完全一样!
Rust 说:“别写两个!用一个‘万能函数’搞定!”
泛型登场!
我们给函数加个“类型占位符”——T
:
fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest { // 等等!这里有问题!
largest = item;
}
}
largest
}
T
是“某种类型”,具体是谁?调用时再定!
调用它:
let nums = vec![34, 50, 25, 100, 65];
let max = largest(&nums); // T = i32
let chars = vec!['y', 'm', 'a', 'q'];
let max = largest(&chars); // T = char
一个函数,通吃所有类型!
但……编译报错:
error[E0369]: 不能对 &T 使用 `>` 操作!
问题来了:不是所有类型都能比较大小啊!你能比较两个
File
谁更大吗?不能!
解法:加个“能力认证”——Trait 约束!
我们告诉 Rust:
“
T
必须会‘比较大小’这个技能!”
use std::cmp::PartialOrd;
fn largest<T: PartialOrd>(list: &[T]) -> &T {
// ...
}
T: PartialOrd
意思是:“T
必须实现了PartialOrd
trait(能力证书)!” 现在编译通过!安全又灵活!
第二关:结构体泛型——打造“万能容器”
场景:点坐标 Point
我们想表示一个点,但坐标可能是整数、小数、字符串……甚至 emoji!
方案一:所有坐标同类型
struct Point<T> {
x: T,
y: T,
}
let p1 = Point { x: 5, y: 10 }; // T = i32
let p2 = Point { x: 1.0, y: 4.0 }; // T = f64
漂亮!但……
let p3 = Point { x: 5, y: 4.0 }; // 报错!x 和 y 类型不同!
不行!
T
只能代表一种类型。
方案二:允许不同类型!
用两个泛型参数:
struct Point<T, U> {
x: T,
y: U,
}
let p1 = Point { x: 5, y: 10 }; // T=i32, U=i32
let p2 = Point { x: 1.0, y: 'a' }; // T=f64, U=char
let p3 = Point { x: "Hi", y: vec![1,2,3] }; // T=&str, U=Vec<i32>
自由!灵活!想放啥放啥! 小贴士:泛型太多?代码会变“天书”!一般别超过 3 个,否则该拆分模块了!
第三关:枚举泛型——“可选值”与“结果”的秘密
还记得 Option<T>
和 Result<T, E>
吗?它们就是泛型的王者!
Option<T>
:可能有值,也可能没有
enum Option<T> {
Some(T),
None,
}
T
是“可能存在的值的类型”
Some(5)
→T
是i32
Some("hello")
→T
是&str
None
→ 没有值,但类型还是Option<T>
Result<T, E>
:成功或失败
enum Result<T, E> {
Ok(T),
Err(E),
}
T
是成功时的值类型(比如File
)
E
是错误时的值类型(比如io::Error
)
一个枚举,表达“两种可能”,类型完全自由!
第四关:方法泛型——给“万能结构体”加技能
我们可以在泛型结构体上定义方法!
示例 1:通用方法
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
impl<T>
表示:这个实现也是泛型的!所有Point<T>
实例都有.x()
方法!
示例 2:只给特定类型加技能
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
这个方法只属于
Point<f32>
!
Point<i32>
没有这个方法,因为它不能开平方!
示例 3:方法自己也有泛型!
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
这个方法更牛:它接受另一个
Point<V, W>
,
然后返回一个“混血”点:x
来自自己,y
来自别人!
let p1 = Point { x: 5, y: 10.4 }; // T=i32, U=f64
let p2 = Point { x: "Hello", y: 'c' }; // V=&str, W=char
let p3 = p1.mixup(p2); // p3.x = 5 (i32), p3.y = 'c' (char)
// 类型:Point<i32, char>
跨类型“基因重组”!太酷了!
终极揭秘:泛型真的慢吗?——单态化魔法!
很多人担心:
“泛型是‘通用’的,是不是运行时要‘查类型’?会不会变慢?”
Rust 大笑:
“不存在的!我们用的是 单态化(Monomorphization) 魔法!”
魔法原理:
编译时,Rust 把泛型代码“展开”成多个具体版本!
比如:
let integer = Some(5); // T = i32
let float = Some(5.0); // T = f64
编译器会生成:
enum Option_i32 { Some(i32), None }
enum Option_f64 { Some(f64), None }
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
结果:运行时没有“泛型”!只有具体类型!
性能 ≈ 你手动写了两个函数!
就像“预制菜”:
你写的是“通用食谱”(泛型),
但上桌的是“定制大餐”(具体类型)!
总结:泛型四重奏
类型 | 语法 | 关键点 |
---|---|---|
函数 | fn func<T>(x: T) |
T: Trait 约束行为 |
结构体 | struct S<T> { x: T } |
多参数 T, U 更灵活 |
枚举 | enum E<T> { V(T) } |
Option 和 Result 的秘密 |
方法 | impl<T> S<T> { ... } |
可通用,可特化,可自泛型 |