第五章:泛型与Trait——代码复用的艺术
本章导读:好的代码应该一次编写,处处适用。但如何在保持灵活性的同时确保类型安全?Rust 的答案是泛型和 Trait。泛型让你编写适用于多种类型的代码,Trait 让你定义共享的行为。本章将揭示 Rust 如何通过这两者的结合,实现零成本的多态。
🧩 5.1 泛型:参数化类型
泛型是一种将类型作为参数的机制,让你编写可以处理多种类型的通用代码。
📦 5.1.1 泛型函数
// 接收任何类型的向量,返回最大值
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, 12, 100, 65];
println!("最大数字: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("最大字符: {}", largest(&chars));
}
` 声明这是一个泛型函数,T 是类型参数。: PartialOrd 是一个 trait 约束,表示 T` 必须支持比较操作。
为什么需要 trait 约束? 泛型
T可以是任何类型,但不是所有类型都能比较大小。PartialOrdtrait 约束告诉编译器:只有实现了比较操作的类型才能使用这个函数。
🏗️ 5.1.2 泛型结构体
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// 为特定类型实现特化方法
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let integer_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };
println!("integer_point.x = {}", integer_point.x());
println!("distance = {}", float_point.distance_from_origin());
}
🎭 5.1.3 多类型参数
struct Point<T, U> {
x: T,
y: U,
}
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,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
🔬 5.1.4 泛型枚举
标准库中有很多泛型枚举:
// Option<T>
enum Option<T> {
Some(T),
None,
}
// Result<T, E>
enum Result<T, E> {
Ok(T),
Err(E),
}
🎯 5.2 Trait:共享行为
Trait 定义了一组方法签名,描述某种行为。任何类型都可以实现 Trait,从而拥有这种行为。
📝 5.2.1 定义和实现 Trait
// 定义 Trait
trait Summary {
fn summarize(&self) -> String;
// 可以有默认实现
fn author(&self) -> String {
String::from("未知作者")
}
}
// 定义结构体
struct Article {
title: String,
content: String,
author: String,
}
struct Tweet {
username: String,
content: String,
}
// 为 Article 实现 Trait
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} - {}", self.title, self.author)
}
// 覆盖默认实现
fn author(&self) -> String {
format!("作者: {}", self.author)
}
}
// 为 Tweet 实现 Trait
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
// 使用默认的 author 实现
}
fn main() {
let article = Article {
title: String::from("Rust 入门"),
content: String::from("Rust 是一门系统编程语言..."),
author: String::from("张三"),
};
let tweet = Tweet {
username: String::from("rustdev"),
content: String::from("今天学 Rust!"),
};
println!("{}", article.summarize());
println!("{}", tweet.summarize());
println!("{}", tweet.author()); // 使用默认实现
}
Trait vs 接口:如果你来自 Java 或 C#,可能会把 Trait 等同于接口。它们确实相似,但有一些关键区别:Rust 允许为外部类型实现外部 Trait(孤儿规则除外),这使得扩展性更强;Rust 的 Trait 可以有关联类型和关联常量。
🏠 5.2.2 Trait 作为参数
// impl Trait 语法
fn notify(item: &impl Summary) {
println!("通知: {}", item.summarize());
}
// Trait Bound 语法(等价但更灵活)
fn notify2<T: Summary>(item: &T) {
println!("通知: {}", item.summarize());
}
// 多个 Trait 约束
fn notify3(item: &(impl Summary + Display)) {
// ...
}
// where 子句(更清晰)
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}
🔄 5.2.3 返回 Trait
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
Article {
title: String::from("Rust"),
content: String::from("内容"),
author: String::from("作者"),
}
} else {
Tweet {
username: String::from("user"),
content: String::from("推文"),
}
}
}
限制:
impl Trait返回类型只能返回单一具体类型。如果函数可能返回Article或Tweet(取决于运行时条件),需要使用 Trait 对象Box。
🎨 5.2.4 条件实现
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
// 对所有 T 实现 new
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
// 只对实现了 Display + PartialOrd 的 T 实现 cmp_display
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("x >= y: {} >= {}", self.x, self.y);
} else {
println!("x < y: {} < {}", self.x, self.y);
}
}
}
🏆 5.3 标准库中的重要 Trait
Rust 标准库定义了许多核心 Trait,理解它们对于编写惯用代码至关重要。
🔄 5.3.1 Clone 和 Copy
// Clone:显式深拷贝
let s1 = String::from("hello");
let s2 = s1.clone(); // 显式调用
// Copy:隐式拷贝(只适用于栈上简单类型)
let x = 5;
let y = x; // 自动复制
Copy 是 Clone 的子集,表示类型可以通过简单的位复制来复制。实现 Copy 的类型在赋值时会自动复制,而不是移动。
📋 5.3.2 Debug 和 Display
use std::fmt;
// Debug:用于调试,可用 {:?} 或 {:#?} 打印
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
// Display:用于用户友好的输出,用 {} 打印
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("Debug: {:?}", p); // Debug: Point { x: 10, y: 20 }
println!("Display: {}", p); // Display: (10, 20)
}
🔍 5.3.3 PartialEq 和 Eq
#[derive(PartialEq, Eq)]
struct Person {
id: u32,
name: String,
}
fn main() {
let p1 = Person { id: 1, name: String::from("张三") };
let p2 = Person { id: 1, name: String::from("张三") };
if p1 == p2 {
println!("是同一个人");
}
}
PartialEq 允许部分相等比较(如浮点数的 NaN),Eq 要求完全的相等关系。
📊 5.3.4 PartialOrd 和 Ord
用于比较大小:
use std::cmp::Ordering;
#[derive(Eq, PartialEq)]
struct Person {
name: String,
age: u32,
}
impl PartialOrd for Person {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.age.cmp(&other.age))
}
}
impl Ord for Person {
fn cmp(&self, other: &Self) -> Ordering {
self.age.cmp(&other.age)
}
}
🏭 5.3.5 From 和 Into
用于类型转换:
let s = String::from("hello"); // From<&str> for String
let num: i32 = "42".parse().unwrap(); // FromStr trait
// Into 自动获得(实现了 From 就自动实现 Into)
fn print_it(input: impl Into<String>) {
let s = input.into();
println!("{}", s);
}
print_it("hello"); // &str 自动转换为 String
🏃 5.3.6 Default
提供默认值:
#[derive(Default)]
struct Config {
debug: bool, // 默认 false
port: u16, // 默认 0
host: String, // 默认空字符串
}
fn main() {
let config = Config::default();
println!("debug: {}, port: {}", config.debug, config.port);
// 使用 .. 语法部分覆盖
let config = Config {
port: 8080,
..Default::default()
};
}
⚡ 5.4 Trait 对象:动态多态
Trait 对象允许在运行时处理不同具体类型的值,实现动态分发。
🎭 5.4.1 什么是 Trait 对象?
// 静态分发(编译时确定具体类型)
fn static_dispatch(item: &impl Summary) {
println!("{}", item.summarize());
}
// 动态分发(运行时确定具体类型)
fn dynamic_dispatch(item: &dyn Summary) {
println!("{}", item.summarize());
}
dyn Summary 是一个 Trait 对象。它使用虚函数表(vtable)在运行时查找正确的方法实现。
📦 5.4.2 使用场景
// 存储不同类型的集合
let items: Vec<Box<dyn Summary>> = vec![
Box::new(Article { /* ... */ }),
Box::new(Tweet { /* ... */ }),
];
for item in &items {
println!("{}", item.summarize());
}
⚖️ 5.4.3 静态分发 vs 动态分发
| 特性 | 静态分发(泛型) | 动态分发(Trait 对象) |
|---|---|---|
| 性能 | 更快(可内联) | 略慢(虚函数调用) |
| 代码大小 | 可能膨胀 | 更小 |
| 异构集合 | 不支持 | 支持 |
| 编译时检查 | 完全 | 部分 |
选择指南:优先使用静态分发(泛型 +
impl Trait)。只有在需要异构集合或运行时灵活性时才使用 Trait 对象。
🧪 5.5 高级 Trait 特性
🔗 5.5.1 关联类型
trait Container {
type Item; // 关联类型
fn get(&self) -> Option<&Self::Item>;
fn add(&mut self, item: Self::Item);
}
struct Backpack {
items: Vec<String>,
}
impl Container for Backpack {
type Item = String; // 指定具体类型
fn get(&self) -> Option<&Self::Item> {
self.items.last()
}
fn add(&mut self, item: Self::Item) {
self.items.push(item);
}
}
关联类型让 Trait 更加灵活,同时避免了泛型参数爆炸。
🏷️ 5.5.2 关联常量
trait Shape {
const NAME: &'static str;
const SIDES: u32;
fn describe(&self) -> String {
format!("{} 有 {} 条边", Self::NAME, Self::SIDES)
}
}
struct Triangle;
impl Shape for Triangle {
const NAME: &'static str = "三角形";
const SIDES: u32 = 3;
}
🔄 5.5.3 Supertrait
Trait 可以要求实现它的类型同时也实现其他 Trait:
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("*{}", "*".repeat(len));
println!("*{}*", output);
println!("*{}", "*".repeat(len));
}
}
📐 5.5.4 Newtype 模式
为外部类型实现外部 Trait(绕过孤儿规则):
// 无法直接为 Vec<i32> 实现 Display(都是外部类型)
// 使用 newtype 包装
struct Wrapper(Vec<i32>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(", "))
}
}
fn main() {
let w = Wrapper(vec![1, 2, 3]);
println!("{}", w); // [1, 2, 3]
}
🏗️ 5.6 实战:构建一个泛型缓存
让我们综合运用本章知识,构建一个类型安全的缓存系统:
use std::collections::HashMap;
use std::hash::Hash;
use std::time::{Duration, Instant};
/// 缓存条目
struct CacheEntry<T> {
value: T,
expires_at: Instant,
}
impl<T> CacheEntry<T> {
fn new(value: T, ttl: Duration) -> Self {
Self {
value,
expires_at: Instant::now() + ttl,
}
}
fn is_expired(&self) -> bool {
Instant::now() > self.expires_at
}
}
/// 泛型缓存
struct Cache<K, V> {
store: HashMap<K, CacheEntry<V>>,
default_ttl: Duration,
}
impl<K, V> Cache<K, V>
where
K: Eq + Hash + Clone,
{
fn new(default_ttl: Duration) -> Self {
Self {
store: HashMap::new(),
default_ttl,
}
}
fn insert(&mut self, key: K, value: V) {
self.insert_with_ttl(key, value, self.default_ttl);
}
fn insert_with_ttl(&mut self, key: K, value: V, ttl: Duration) {
let entry = CacheEntry::new(value, ttl);
self.store.insert(key, entry);
}
fn get(&mut self, key: &K) -> Option<&V> {
// 清理过期条目
self.cleanup_expired();
self.store.get(key).map(|entry| &entry.value)
}
fn remove(&mut self, key: &K) -> Option<V> {
self.store.remove(key).map(|entry| entry.value)
}
fn cleanup_expired(&mut self) {
let expired: Vec<K> = self.store
.iter()
.filter(|(_, entry)| entry.is_expired())
.map(|(key, _)| key.clone())
.collect();
for key in expired {
self.store.remove(&key);
}
}
fn len(&self) -> usize {
self.store.len()
}
}
fn main() {
let mut cache: Cache<String, String> = Cache::new(Duration::from_secs(60));
cache.insert("name".to_string(), "张三".to_string());
cache.insert_with_ttl("temp".to_string(), "临时数据".to_string(), Duration::from_millis(100));
println!("缓存大小: {}", cache.len());
if let Some(name) = cache.get(&"name".to_string()) {
println!("姓名: {}", name);
}
std::thread::sleep(Duration::from_millis(150));
cache.cleanup_expired();
println!("清理后大小: {}", cache.len());
}
这个例子展示了:
- 泛型结构体:
Cache<K, V>支持任意键值类型 - Trait 约束:
K: Eq + Hash + Clone确保键可用作 HashMap 的键 - 关联类型:虽然这里没用到,但
HashMap内部使用了关联类型 - 内部可变性和生命周期管理
📝 本章小结
本章我们深入学习了 Rust 的泛型和 Trait 系统:
- 泛型让你编写适用于多种类型的代码,编译器为每种具体类型生成专门的代码(单态化)
- Trait 定义共享行为,任何类型都可以实现 Trait
- Trait 约束限制泛型参数必须实现特定的 Trait
- Trait 对象
dyn Trait实现动态分发和异构集合 - 标准库 Trait 如
Clone、Debug、Display、From等是 Rust 生态的基础
关键要点:
- 优先使用静态分发,只在需要时使用动态分发
- 使用
where子句让复杂的 trait 约束更清晰 - 利用
#[derive]自动实现常见 Trait - 理解孤儿规则:只能为本地类型实现本地 Trait
在下一章,我们将学习 Rust 的错误处理机制——如何用 Option 和 Result 优雅地处理可能失败的操作。
动手实验:
- 为上一章的
JsonValue实现Displaytrait,使其能够格式化输出。- 实现一个泛型函数
first_match<T, P>(items: &[T], predicate: P) -> Option<&T>,其中P是一个返回bool的闭包。- 定义一个
Drawabletrait,包含draw(&self)方法。为不同的形状(圆、矩形)实现这个 trait,然后用Vec<Box<dyn Drawable>>存储并绘制它们。