泛型(Generics)
为什么需要泛型?
泛型让我们能够编写对多种类型都适用的代码,消除重复。假设你需要找出一个 i32 列表中的最大值,再写一个找 f64 列表最大值的函数——如果没有泛型,代码几乎完全相同,只是类型不同。
// 没有泛型:重复的代码
fn largest_i32(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list { if item > largest { largest = item; } }
largest
}
fn largest_f64(list: &[f64]) -> f64 { /* 完全相同的逻辑 */ }
// 使用泛型:一次编写,适用多种类型
fn largest<T: PartialOrd>(list: &[T]) -> &T {
// T: PartialOrd 是 Trait 约束,说明 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));
let chars = vec!['y', 'm', 'a', 'q'];
println!("最大字符: {}", largest(&chars));
}
泛型结构体
#[derive(Debug)]
struct Pair<T> {
first: T,
second: T,
}
impl<T> Pair<T> {
fn new(first: T, second: T) -> Self {
Pair { first, second }
}
}
// 只为特定约束的 T 实现额外方法
impl<T: std::fmt::Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.first >= self.second {
println!("最大值: {}", self.first);
} else {
println!("最大值: {}", self.second);
}
}
}
fn main() {
let p = Pair::new(5, 10);
p.cmp_display();
// 多个泛型参数
struct Point<T, U> { x: T, y: U }
let p2 = Point { x: 5, y: 4.0 }; // Point<i32, f64>
}
零成本抽象:单态化
Rust 泛型使用单态化(monomorphization)——编译器为每种具体使用的类型生成专用代码。调用 largest::<i32>(...) 和 largest::<char>(...) 会编译成两份独立的机器码,各自优化。
这意味着使用泛型不会有任何运行时开销——编译后的代码和你手写两个具体函数一样高效。这就是 Rust "零成本抽象"的含义。
Trait:共享行为的接口
Trait 是什么?
Trait 类似于其他语言中的接口(Interface)或抽象类,但更灵活。Trait 定义了一组方法签名,实现了该 Trait 的类型承诺提供这些方法的具体实现。Trait 是 Rust 多态和代码复用的核心机制。
// 定义 Trait
trait Summary {
// 抽象方法:必须由实现者提供
fn summarize_author(&self) -> String;
// 默认方法:可以被覆盖,也可以直接使用
fn summarize(&self) -> String {
format!("(作者:{})", self.summarize_author())
}
}
struct Article {
title: String,
author: String,
content: String,
}
struct Tweet {
username: String,
content: String,
}
// 为 Article 实现 Summary
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
// 覆盖默认实现
fn summarize(&self) -> String {
format!("{}: {} ...", self.author, &self.content[..50.min(self.content.len())])
}
}
// 为 Tweet 实现 Summary
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// 不覆盖 summarize,使用默认实现
}
Trait 作为函数参数
// impl Trait 语法:接受任何实现了 Summary 的类型
fn notify(item: &impl Summary) {
println!("新消息: {}", item.summarize());
}
// 等价的 Trait bound 语法(更灵活)
fn notify_generic<T: Summary>(item: &T) {
println!("新消息: {}", item.summarize());
}
// 多个 Trait bound
fn notify_debug<T: Summary + std::fmt::Debug>(item: &T) {
println!("{:?}: {}", item, item.summarize());
}
// where 子句:当约束很多时,让签名更清晰
fn complex_fn<T, U>(t: &T, u: &U) -> String
where
T: std::fmt::Display + Clone,
U: std::fmt::Debug + Summary,
{
format!("{} {:?}", t, u)
}
// 返回实现了某 Trait 的类型(impl Trait in return position)
fn make_summary() -> impl Summary {
Tweet { username: String::from("rust"), content: String::from("hello") }
}
Trait 对象(dyn Trait)
静态分发 vs 动态分发
泛型使用静态分发(static dispatch):在编译期确定具体类型,生成专用代码,无运行时开销。但有时你需要在运行时决定使用哪种类型——例如,一个集合中存放实现了同一 Trait 的不同类型。这时需要动态分发(dynamic dispatch),即 Trait 对象。
use std::fmt;
trait Draw {
fn draw(&self);
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Draw for Circle {
fn draw(&self) { println!("绘制圆形,半径 {}", self.radius); }
}
impl Draw for Square {
fn draw(&self) { println!("绘制正方形,边长 {}", self.side); }
}
// dyn Trait:运行时多态,存放不同类型的 Trait 对象
struct Screen {
components: Vec<Box<dyn Draw>>, // Box 用于堆分配,因为大小在编译期未知
}
impl Screen {
fn render(&self) {
for component in &self.components {
component.draw(); // 通过虚函数表(vtable)调用,运行时决定
}
}
}
fn main() {
let screen = Screen {
components: vec![
Box::new(Circle { radius: 5.0 }),
Box::new(Square { side: 3.0 }),
Box::new(Circle { radius: 2.0 }),
],
};
screen.render();
}
何时选择 impl Trait vs dyn Trait?
impl Trait(静态分发):性能最优,集合中所有元素类型相同,编译期确定类型。
dyn Trait(动态分发):集合中需要存放不同类型(但实现了同一 Trait),或者函数需要在运行时决定返回哪种具体类型。代价是轻微的运行时开销(虚函数表查找)和堆分配(通常需要 Box)。
常用标准 Trait
最重要的几个 Trait
Display
用户友好的格式化输出,对应
{} 占位符。需要手动实现,表达"这个类型如何向用户展示"。Debug
调试输出,对应
{:?}。可通过 #[derive(Debug)] 自动派生,用于调试和日志。Clone / Copy
Clone 允许显式深拷贝;Copy 标记栈上可自动复制的类型(见第3章)。
PartialEq / Eq
相等性比较(
== 和 !=)。Eq 是 PartialEq 的子集,表示完全等价关系(f64 因 NaN 问题只能实现 PartialEq)。PartialOrd / Ord
大小比较(
< > <= >=)。Ord 表示全序关系,可用于排序。Iterator
迭代器协议,只需实现
next() 方法,就能使用所有迭代器适配器(map/filter/fold 等)。Rust 最强大的抽象之一。From / Into
类型转换。实现
From<A> for B 后,自动获得 Into<B> for A。? 运算符依赖 From 进行错误类型转换。Default
提供类型的默认值(
Default::default())。可通过 #[derive(Default)] 派生。use std::fmt;
#[derive(Debug, Clone, PartialEq)]
struct Point { x: f64, y: f64 }
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl Default for Point {
fn default() -> Self {
Point { x: 0.0, y: 0.0 }
}
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
println!("{}", p); // Display: (3, 4)
println!("{:?}", p); // Debug: Point { x: 3.0, y: 4.0 }
let origin = Point::default();
println!("{}", origin); // (0, 0)
println!("{}", p == p.clone()); // true
}