Chapter 07

泛型、Trait 与多态

Rust 的零成本抽象核心——泛型在编译期单态化,Trait 提供灵活的行为约束

泛型(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
}