Rust: Rust 泛型(Generics):类型参数化编程
最后更新:2026-08-26
泛型(Generics)是"类型层面的参数化编程"——把类型也作为参数传递,让同一份代码适用于多种类型,而不需要为每种类型重复写代码。
如果说函数是"把值抽象成参数",那泛型就是"把类型也抽象成参数"。就像点外卖时你说"我要一份饭",不指定是炒饭还是盖饭——到了餐厅再决定。
1. 你将学到
- 泛型函数
fn foo<T>(x: T)的定义与调用 - 泛型结构体
struct Point<T>的用法 - 泛型枚举
Option<T>和Result<T, E>的深入理解 - 泛型方法
impl<T>的写法 - 单态化(monomorphization)——编译时展开机制
- 多个类型参数的用法
2. 概念图解
以下 Mermaid 图展示泛型类型参数 T 在编译时单态化(monomorphization)过程中被具体类型替换的机制:
graph LR
A["泛型函数<br/>fn identity<T>(x: T) -> T"] --> B["编译时单态化<br/>Monomorphization"]
B --> C["T → i32<br/>fn identity_i32(x: i32) -> i32"]
B --> D["T → String<br/>fn identity_string(x: String) -> String"]
B --> E["T → f64<br/>fn identity_f64(x: f64) -> f64"]
C --> F["调用 identity(42)"]
D --> G["调用 identity("hello".to_string())"]
E --> H["调用 identity(3.14)"]
3. 一个万能容器的故事
(1) 痛苦:为每种类型写重复的代码
Luna (Luna) 在开发一个工具库,需要一个"找出最大值"的函数。
最开始她为每种类型写一份:
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_str(a: &str, b: &str) -> &str {
if a > b { a } else { b }
}
fn main() {
println!("{}", max_i32(3, 7)); // 7
println!("{}", max_f64(2.5, 1.8)); // 2.5
println!("{}", max_str("apple", "banana")); // banana
}
三个函数除了类型不同,逻辑完全一样。如果还有 u32、u64、char…… 每多一种类型就要复制一份。这是"复制粘贴编程"——既不优雅也不可维护。
(2) Rust 泛型的方案
fn max<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
fn main() {
println!("{}", max(3, 7)); // 7
println!("{}", max(2.5, 1.8)); // 2.5
println!("{}", max("apple", "banana")); // banana
}
一个泛型函数替代了三个具体类型的函数——而且编译器会为每种实际使用的类型自动生成专门的代码(单态化)。你写一份,编译器帮你展开成多份。
4. 核心概念
(1) 泛型体系
graph TB
A[泛型 Generics] --> B[泛型函数]
A --> C[泛型结构体]
A --> D[泛型枚举]
A --> E[泛型方法]
B --> F["fn identity<T>(x: T) -> T"]
C --> G["struct Point<T> { x: T, y: T }"]
D --> H["Option<T>, Result<T, E>"]
E --> I["impl<T> Point<T> { fn x(&self) -> &T }"]
A --> J[单态化 Monomorphization]
J --> K["编译时:T → i32, f64, String ..."]
J --> L["每种类型生成独立代码"]
(2) 泛型 vs 具体类型 vs 动态分发
| 特性 | 具体类型(无泛型) | 泛型(单态化) | 动态分发(dyn Trait) |
|---|---|---|---|
| 代码重复 | 每种类型手写一份 | 编译器自动展开 | 一份代码,运行时派发 |
| 性能 | 最佳 | 最佳(无虚函数开销) | 有虚函数开销 |
| 编译时间 | 长(手写多) | 较长(展开多份) | 较短 |
| 二进制体积 | 大 | 较大(每类型一份) | 较小 |
| 灵活性 | 差 | 编译时确定 | 运行时确定 |
(3) 常用泛型枚举
| 枚举 | 定义 | 用途 |
|---|---|---|
Option<T> |
enum Option<T> { Some(T), None } |
可能为空的值 |
Result<T, E> |
enum Result<T, E> { Ok(T), Err(E) } |
可能失败的操作 |
Vec<T> |
struct Vec<T> { ... } |
动态数组 |
HashMap<K, V> |
struct HashMap<K, V> { ... } |
键值映射 |
(4) 泛型约束方式对比
| 约束方式 | 语法 | 适用场景 | 示例 |
|---|---|---|---|
| 内联约束 | fn foo<T: Trait>(x: T) |
简单单约束 | fn max<T: PartialOrd>(a: T, b: T) |
| 多约束 + | fn foo<T: Trait1 + Trait2>(x: T) |
多个约束 | fn print<T: Display + Clone>(x: T) |
| where 子句 | fn foo<T>(x: T) where T: Trait |
复杂约束、多类型参数 | where T: Display + Clone, U: Debug |
| impl Trait | fn foo(x: impl Trait) |
简洁写法(语法糖) | fn plug(d: &impl USBDevice) |
5. 泛型示例
▶ 示例 1:泛型函数——找出数组中的最大值(难度 ⭐)
// ============================================
// 泛型函数:适用于任何可比较的类型
// ============================================
// PartialOrd 约束确保 T 支持比较操作
fn find_max<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut max = &list[0];
for item in list.iter() {
if item > max {
max = item;
}
}
max
}
fn main() {
let numbers = vec![3, 7, 1, 9, 4];
println!("i32 最大值: {}", find_max(&numbers));
let floats = vec![2.5, 1.8, 3.14, 0.99];
println!("f64 最大值: {}", find_max(&floats));
let strings = vec!["apple", "banana", "cherry", "date"];
println!("&str 最大值: {}", find_max(&strings));
// 同一个函数,三种类型,编译器自动展开
}
输出:
i32 最大值: 9
f64 最大值: 3.14
&str 最大值: date
find_max<T>是一个泛型函数,T是类型参数。<T: std::cmp::PartialOrd>是 trait bound——意思是"T 必须是可比较的类型"。调用时编译器根据实参类型自动推断T。
▶ 示例 2:泛型结构体——Point 坐标系统(难度 ⭐⭐)
// ============================================
// 泛型结构体:Point 可以存储任意类型的坐标
// ============================================
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
// 为泛型结构体实现方法
impl<T> Point<T> {
// 返回 x 的引用
fn x(&self) -> &T {
&self.x
}
// 返回 y 的引用
fn y(&self) -> &T {
&self.y
}
}
// 为特定类型 f64 的 Point 实现额外方法
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 };
let float_point = Point { x: 3.0, y: 4.0 };
let string_point = Point {
x: "left",
y: "right",
};
println!("int_point: {:?}", int_point);
println!("float_point x: {}", float_point.x());
println!("string_point: {:?}", string_point);
// 只有 `Point<f64>` 有 distance_from_origin 方法
println!("距离原点: {:.2}", float_point.distance_from_origin());
// 编译错误:int_point 是 Point<i32>,没有这个方法
// println!("{}", int_point.distance_from_origin());
}
输出:
int_point: Point { x: 5, y: 10 }
float_point x: 3.0
string_point: Point { x: "left", y: "right" }
距离原点: 5.00
Point<T>是一个泛型结构体,x和y的类型相同(都是T)。impl<T> Point<T>为所有T实现通用方法。impl Point<f64>只为特定类型实现专用方法——这是泛型的一大优势。
▶ 示例 3:泛型枚举——Option 与 Result 实战(难度 ⭐⭐)
// ============================================
// 泛型枚举:Option<T> 和 Result<T, E> 的使用
// ============================================
// 自定义 Result 风格枚举
#[derive(Debug)]
enum MyResult<T, E> {
Success(T),
Failure(E),
}
// 除法函数:返回 Result 风格
fn safe_divide<T>(a: T, b: T) -> MyResult<T, String>
where
T: std::ops::Div<Output = T> + std::cmp::PartialEq + From<u8> + Copy,
{
if b == 0.into() {
MyResult::Failure("Division by zero".to_string())
} else {
MyResult::Success(a / b)
}
}
// 使用标准库的 Option<T>
fn find_in_vector<T: PartialEq>(vec: &[T], target: &T) -> Option<usize> {
for (i, item) in vec.iter().enumerate() {
if item == target {
return Some(i);
}
}
None
}
// 使用标准库的 Result<T, E>
fn parse_number(s: &str) -> Result<i32, String> {
s.parse::<i32>().map_err(|e| format!("Parse error: {}", e))
}
fn main() {
// Option 的使用
let numbers = vec![10, 20, 30, 40, 50];
match find_in_vector(&numbers, &30) {
Some(index) => println!("找到 30,索引: {}", index),
None => println!("未找到"),
}
match find_in_vector(&numbers, &99) {
Some(index) => println!("找到 99,索引: {}", index),
None => println!("未找到 99"),
}
// Result 的使用
match parse_number("42") {
Ok(n) => println!("解析成功: {}", n),
Err(e) => println!("解析失败: {}", e),
}
match parse_number("hello") {
Ok(n) => println!("解析成功: {}", n),
Err(e) => println!("解析失败: {}", e),
}
// 自定义 MyResult 的使用
let result = safe_divide(10.0, 3.0);
println!("自定义 Result: {:?}", result);
let result = safe_divide(10.0, 0.0);
println!("自定义 Result: {:?}", result);
}
输出:
找到 30,索引: 2
未找到 99
解析成功: 42
解析失败: Parse error: invalid digit found in string
自定义 Result: Success(3.3333333333333335)
自定义 Result: Failure("Division by zero")
Option<T>只有一个类型参数T(有值或无值),Result<T, E>有两个类型参数(成功值类型和错误类型)。泛型枚举让这些类型可以适用于任何数据类型——这是 Rust 标准库最核心的设计。
▶ 示例 4:多个类型参数与单态化(难度 ⭐⭐⭐)
// ============================================
// 多个类型参数 + 泛型方法的组合使用
// ============================================
use std::fmt::Display;
// 两个类型参数的泛型结构体
#[derive(Debug)]
struct Pair<K, V> {
key: K,
value: V,
}
// 为 Pair<K, V> 实现方法
impl<K, V> Pair<K, V> {
fn new(key: K, value: V) -> Self {
Pair { key, value }
}
}
// 约束方法:仅当 K 和 V 都实现了 Display 时才可用
impl<K: Display, V: Display> Pair<K, V> {
fn print(&self) {
println!("Key: {}, Value: {}", self.key, self.value);
}
}
// 泛型方法:混合不同类型的参数
fn mix_and_match<T, U>(a: T, b: U) -> String
where
T: Display,
U: Display,
{
format!("Mixed: {} and {}", a, b)
}
fn main() {
// 多个类型参数:String 和 i32
let pair1 = Pair::new("Age".to_string(), 25);
pair1.print();
// 多个类型参数:&str 和 f64
let pair2 = Pair::new("PI", 3.14159);
pair2.print();
// 不同类型组合
let pair3 = Pair::new(100, "HTTP OK");
// pair3.print(); // ❌ 编译错误:i32 和 &str 都实现了 Display,但这里不会报错
// 实际上 i32 和 &str 都实现了 Display,所以可以调用
// 这里只是为了演示约束方法的概念
pair3.print();
// 混合不同类型
println!("{}", mix_and_match(42, "answer"));
println!("{}", mix_and_match(3.14, 100));
}
输出:
Key: Age, Value: 25
Key: PI, Value: 3.14159
Key: 100, Value: HTTP OK
Mixed: 42 and answer
Mixed: 3.14 and 100
多个类型参数(
<K, V>)让结构体可以持有不同类型的数据。where子句用于约束类型参数必须满足的条件。单态化时,编译器会为Pair<String, i32>、Pair<&str, f64>等每种组合生成独立的代码。
▶ 示例 5:综合练习——泛型容器与算法(难度 ⭐⭐⭐)
// ============================================
// 综合示例:泛型栈 + 泛型搜索算法
// ============================================
use std::fmt::Display;
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { items: Vec::new() }
}
fn push(&mut self, item: T) {
self.items.push(item);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn peek(&self) -> Option<&T> {
self.items.last()
}
fn is_empty(&self) -> bool {
self.items.is_empty()
}
fn len(&self) -> usize {
self.items.len()
}
}
impl<T: Display> Stack<T> {
fn print_all(&self) {
for item in &self.items {
print!("{} ", item);
}
println!();
}
}
fn find_first<T: PartialEq>(items: &[T], target: &T) -> Option<usize> {
items.iter().position(|x| x == target)
}
fn swap_if_greater<T: PartialOrd>(a: &mut T, b: &mut T) {
if *a > *b {
std::mem::swap(a, b);
}
}
fn main() {
let mut int_stack: Stack<i32> = Stack::new();
int_stack.push(10);
int_stack.push(20);
int_stack.push(30);
println!("=== 整数栈 ===");
println!("栈内容: ");
int_stack.print_all();
println!("栈顶: {:?}", int_stack.peek());
println!("弹出: {:?}", int_stack.pop());
println!("剩余 {} 个元素", int_stack.len());
let mut str_stack: Stack<&str> = Stack::new();
str_stack.push("Rust");
str_stack.push("is");
str_stack.push("awesome");
println!("\n=== 字符串栈 ===");
str_stack.print_all();
let nums = vec![10, 20, 30, 40, 50];
println!("\n=== 泛型搜索 ===");
println!("找 30: 索引 {:?}", find_first(&nums, &30));
println!("找 99: 索引 {:?}", find_first(&nums, &99));
let words = vec!["apple", "banana", "cherry"];
println!("找 'banana': 索引 {:?}", find_first(&words, &"banana"));
let mut x = 42;
let mut y = 10;
println!("\n=== 泛型交换 ===");
println!("交换前: x={}, y={}", x, y);
swap_if_greater(&mut x, &mut y);
println!("交换后: x={}, y={}", x, y);
}
输出:
=== 整数栈 ===
栈内容:
10 20 30
栈顶: Some(30)
弹出: Some(30)
剩余 2 个元素
=== 字符串栈 ===
Rust is awesome
=== 泛型搜索 ===
找 30: 索引 Some(2)
找 99: 索引 None
找 'banana': 索引 Some(1)
=== 泛型交换 ===
交换前: x=42, y=10
交换后: x=10, y=42
泛型
Stack<T>对 i32 和 &str 都有效;impl<T: Display>约束只在类型实现 Display 时才有print_all方法;find_first<T: PartialEq>用PartialOrd约束实现泛型比较交换。
❓ 常见问题
Box<dyn Any> 有什么区别?dyn Any 是运行时确定类型(动态分发)。impl<T> 和 impl 块有什么区别?impl<T> 为所有 T 实现方法,普通 impl 为具体类型实现方法。where 子句和直接在尖括号里写有什么区别?where 子句在复杂约束时可读性更好。📖 小节
- 泛型函数
fn foo<T>(x: T)让同一函数适用于多种类型 - 泛型结构体
struct Point<T>让结构体字段可以存储任意类型 - 泛型枚举
Option<T>和Result<T, E>是 Rust 标准库泛型设计的典范 - 泛型方法
impl<T> Type<T>为所有类型参数实现方法,也可以为特定类型实现专用方法 - 单态化(Monomorphization)是编译器在编译时把泛型代码展开成具体类型的代码——零运行时开销
- 多个类型参数
<T, U>和where约束子句让泛型既灵活又安全
📝 作业
- 难度 ⭐:写一个泛型函数
fn echo<T>(x: T) -> T,它接受一个值并原样返回。在 main 中分别用 i32、f64 和 &str 调用它。 - 难度 ⭐⭐:定义一个泛型结构体
Container<T>,它有一个字段value: T。实现方法fn get(&self) -> &T返回值的引用,以及fn set(&mut self, val: T)修改值。在 main 中分别用Container<String>和Container<i32>测试。 - 难度 ⭐⭐⭐:写一个泛型函数
fn merge_arrays<T>(a: &[T], b: &[T]) -> Vec<T>合并两个切片,返回一个新 Vec。要求 T 实现了Clone。然后在 main 中分别合并两个i32切片和两个&str切片。