泛型教程
Rust 的泛型(generics)允许你编写抽象、可重用的代码,而不牺牲性能。它类似于其他语言的模板或泛型,但 Rust 的泛型是零成本抽象:在编译时单态化(monomorphization),生成具体类型的代码。这确保了类型安全,同时避免运行时开销。泛型常用于函数、结构体、枚举和 trait 中,帮助创建如 Vec
1. 泛型简介
- 什么是泛型?:使用类型参数(如
)定义代码,允许在不同类型上重用。T 是占位符,在使用时替换为具体类型。 - 优势:代码复用、类型安全、性能高(编译时展开)。
- 语法:在函数、struct 等后用 <参数>,如 fn foo
(arg: T)。 - 与 trait 的关系:泛型常结合 trait bound(如 T: Clone)限制类型。
示例:简单泛型函数
#![allow(unused)] fn main() { fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // 错误!T 可能不支持 > largest = item; } } largest } }
- 解释:这个会编译错误,因为 T 未指定支持比较。需要 trait bound(见下文)。
2. 泛型函数
函数可以有泛型参数。
示例:泛型函数
fn print_value<T>(value: T) { println!("值: {:?}", value); // 错误!T 需实现 Debug } fn main() { print_value(5); // T = i32 print_value("hello"); // T = &str }
- 解释:编译错误,因为 println! 需要 Debug。添加 bound 修复(见第 4 节)。
3. 泛型结构体和枚举
结构体和枚举可以泛型化。
示例:泛型结构体
#[derive(Debug)] struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 }; println!("整数点: {:?}", integer_point); println!("浮点: {:?}", float_point.x()); }
- 解释:Point
为 T 生成具体类型。impl 为所有 T 实现方法。多参数如 <T, U> 允许不同类型,如 Point { x: 5, y: 3.14 }。
示例:泛型枚举
enum Option<T> { // 标准库中的简化版 Some(T), None, } fn main() { let some_number = Option::Some(5); let absent: Option<i32> = Option::None; }
- 解释:枚举变体持泛型值。标准库 Option
和 Result<T, E> 是泛型枚举。
4. Trait Bound
Bound 限制泛型参数必须实现某些 trait。
- 语法:fn foo<T: Trait1 + Trait2>(arg: T)
- 常见 bound:Copy、Clone、Debug、PartialEq、PartialOrd 等。
示例:带 bound 的函数
use std::fmt::Debug; fn print_value<T: Debug>(value: T) { println!("值: {:?}", value); } fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest } fn main() { let numbers = vec![34, 50, 25, 100, 65]; println!("最大: {}", largest(&numbers)); // 输出: 最大: 100 }
- 解释:T: PartialOrd 确保 > 操作符可用。多 bound 用 +,如 T: Debug + Clone。
5. Where 子句
对于复杂 bound,用 where 子句提高可读性。
示例:Where 子句
#![allow(unused)] fn main() { fn some_function<T, U>(t: T, u: U) -> U where T: Debug + Clone, U: Clone + PartialEq, { if t.clone() == u { // 错误!T 和 U 类型不同,不能比较 // ... } u } }
- 解释:where 在签名后。适用于函数、impl、trait。
6. 泛型 impl 和 trait
impl 可以泛型,trait 可以定义泛型方法。
示例:泛型 impl
#![allow(unused)] fn main() { impl<T: Debug> Point<T> { // 只为 Debug 类型实现 fn debug_print(&self) { println!("{:?}", self); } } }
- 解释:bound 限制 impl 范围。
示例:泛型 trait
#![allow(unused)] fn main() { trait Summary<T> { fn summarize(&self) -> T; } }
- 解释:trait 本身可以泛型,但常见是方法内用泛型。
7. 高级主题:关联类型和生命周期
- 关联类型:在 trait 中定义类型占位符,避免过多泛型。
#![allow(unused)] fn main() { trait Iterator { type Item; // 关联类型 fn next(&mut self) -> Option<Self::Item>; } }
- 泛型与生命周期:结合 'a,如 fn longest<'a, T>(x: &'a T, y: &'a T) -> &'a T。
- 性能:单态化生成具体代码,可能增加二进制大小,但运行时零开销。
8. 最佳实践和常见陷阱
- 使用 bound 最小化:只添加必要 trait,避免过度限制。
- 优先具体类型:泛型用于真正需要重用时。
- ** turbofish 语法**:指定类型如 Vec::
::new(),当推断失败时用。 - 常见错误:
- 未 bound:操作如 + 时错误(添加 T: Add)。
- 类型不匹配:如混合 T 和 U 时,确保兼容。
- 过度泛型:导致代码复杂,考虑 trait 对象(dyn Trait)用于运行时多态(有开销)。
- 编译时间长:过多泛型展开,优化 bound 或用 Box
。
- 标准库示例:Vec
、HashMap<K, V> – 研究它们的 impl。
练习建议
- 编写泛型函数,接收 &[T] 并返回反转切片(需 T: Clone)。
- 创建泛型 struct Pair
,实现方法如 swap()(需 T: Copy)。 - 定义 trait Printable
,为不同类型实现,并用泛型函数调用。