第六章 Trait 与泛型:Rust 的多态#
"Program to an interface, not an implementation." — Gang of Four
在 C++ 中我们用虚函数和模板实现多态,在 Java 中我们用 interface 和泛型。Rust 的答案是 Trait + 泛型——它既能做到零成本的静态分发,也支持灵活的动态分发,而且在编译期就能捕获大多数类型错误。
本章将带你全面掌握 Rust 的多态机制,并与你熟悉的 C++/Java 概念逐一对照。
mindmap
root((Trait 与泛型))
Trait
共享行为
默认实现
trait bound
泛型
类型参数
where 约束
单态化
分发
静态分发
dyn Trait
对象安全
对比
Java interface
C++ template
Go interface
6.1 什么是 Trait?#
Trait 是 Rust 中定义共享行为的方式。你可以把它理解为:
Java 的
interface(但可以有默认实现)C++ 的纯虚基类 + Concepts(C++20)
Go 的
interface(但是显式实现,非鸭子类型)
6.1.1 定义一个 Trait#
/// 任何可以被摘要展示的类型
trait Summary {
fn summarize(&self) -> String;
// 默认实现——实现者可以选择覆盖
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..20.min(self.summarize().len())])
}
}
6.1.2 为类型实现 Trait#
struct Article {
title: String,
author: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
struct Tweet {
username: String,
text: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.text)
}
}
使用:
fn main() {
let article = Article {
title: String::from("Rust 2024"),
author: String::from("Walter"),
content: String::from("Rust is awesome..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
text: String::from("Rust 1.80 is out!"),
};
println!("{}", article.summarize());
println!("{}", tweet.summarize());
}
6.1.3 方法接收者:&self、&mut self 与 self#
Trait 方法的第一个参数经常写成 &self、&mut self 或 self。它们不是三种不同的对象模型,而是三种所有权选择:
接收者 |
含义 |
适合场景 |
|---|---|---|
|
不可变借用当前对象 |
只读查询、格式化、计算派生值 |
|
可变借用当前对象 |
修改内部状态,但不消费对象 |
|
获取当前对象所有权 |
消费对象、转换对象、链式 builder 结束 |
trait Cache {
fn name(&self) -> &str;
fn put(&mut self, key: String, value: String);
fn into_entries(self) -> Vec<(String, String)>;
}
对调用者来说,这三种写法直接决定对象还能不能继续用:
use std::collections::HashMap;
struct MemoryCache {
name: String,
entries: HashMap<String, String>,
}
impl Cache for MemoryCache {
fn name(&self) -> &str {
&self.name
}
fn put(&mut self, key: String, value: String) {
self.entries.insert(key, value);
}
fn into_entries(self) -> Vec<(String, String)> {
self.entries.into_iter().collect()
}
}
fn main() {
let mut cache = MemoryCache {
name: "local".into(),
entries: HashMap::new(),
};
println!("{}", cache.name()); // &self:cache 仍可继续用
cache.put("theme".into(), "dark".into()); // &mut self:修改 cache
let entries = cache.into_entries(); // self:cache 被消费
// println!("{}", cache.name()); // ❌ cache 已经移动
println!("{:?}", entries);
}
经验法则:默认先选 &self;需要改状态时选 &mut self;方法的语义是“把这个对象变成另一个东西”时才选 self。
6.1.4 三语对比:定义与实现#
特性 |
Rust |
C++ |
Java |
|---|---|---|---|
定义接口 |
|
|
|
实现接口 |
|
|
|
默认实现 |
✅ trait 中直接写 |
✅ 虚函数提供默认实现 |
✅ |
为外部类型实现 |
✅(孤儿规则限制下) |
❌ 需要继承或 adapter |
❌ 不可能 |
多重实现 |
✅ 一个类型可 impl 多个 trait |
✅ 多重继承(菱形问题) |
✅ 多个 interface |
6.2 Trait Bound 与泛型#
6.2.1 泛型函数#
// 接受任何实现了 Summary 的类型
fn notify(item: &impl Summary) {
println!("Breaking: {}", item.summarize());
}
这是语法糖,完整写法是 Trait Bound:
fn notify<T: Summary>(item: &T) {
println!("Breaking: {}", item.summarize());
}
6.2.2 多个 Trait Bound#
use std::fmt::Display;
fn notify_and_display<T: Summary + Display>(item: &T) {
println!("{}", item.summarize());
println!("{}", item); // 要求 T 同时实现 Display
}
当约束变多时,使用 where 子句更清晰:
fn complex_function<T, U>(t: &T, u: &U) -> String
where
T: Summary + Clone,
U: Display + Debug,
{
format!("{} - {:?}", t.summarize(), u)
}
6.2.3 三语对比:泛型约束#
特性 |
Rust |
C++ |
Java |
|---|---|---|---|
泛型语法 |
|
|
|
约束 |
|
|
|
编译时检查 |
✅ 严格 |
✅ (concept) / ❌ (模板错误信息差) |
✅ 类型擦除后有限检查 |
单态化 |
✅ 每个具体类型生成一份代码 |
✅ 模板实例化 |
❌ 类型擦除,运行时一份代码 |
graph LR
A[泛型函数 notify<T>] -->|T = Article| B[notify_Article 单态化版本]
A -->|T = Tweet| C[notify_Tweet 单态化版本]
style A fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style C fill:#bbf,stroke:#333
单态化(Monomorphization):Rust 编译器为每个具体类型生成专用代码,因此泛型调用的性能等同于手写具体类型的代码——这就是"零成本抽象"。
6.3 静态分发 vs 动态分发#
这是 Rust 多态最核心的设计决策。
6.3.1 静态分发(impl Trait / 泛型)#
fn print_summary(item: &impl Summary) {
println!("{}", item.summarize());
}
编译时确定具体类型
生成专用代码(单态化)
零运行时开销
代价:二进制体积可能增大
6.3.2 动态分发(dyn Trait)#
fn print_summary_dyn(item: &dyn Summary) {
println!("{}", item.summarize());
}
通过 虚表(vtable) 在运行时查找方法
只生成一份代码
有一次指针间接寻址的开销
适合需要异构集合的场景
fn main() {
let article = Article {
title: String::from("Rust Traits"),
author: String::from("Walter"),
content: String::from("..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
text: String::from("Hello!"),
};
// 异构集合:不同类型放在同一个 Vec 中
let items: Vec<Box<dyn Summary>> = vec![
Box::new(article),
Box::new(tweet),
];
for item in &items {
println!("{}", item.summarize());
}
}
6.3.3 dyn Trait 的内存布局#
graph TD
subgraph "Box<dyn Summary> = 胖指针"
P1[数据指针] --> D[Article 实例数据]
P2[vtable 指针] --> V[vtable]
end
subgraph "vtable"
V --> M1["summarize() 函数指针"]
V --> M2["preview() 函数指针"]
V --> M3["drop() 函数指针"]
V --> M4["size / align"]
end
style P1 fill:#fbb,stroke:#333
style P2 fill:#bfb,stroke:#333
6.3.4 三语对比:分发机制#
分发方式 |
Rust |
C++ |
Java |
|---|---|---|---|
静态分发 |
|
模板 |
不支持(泛型是类型擦除) |
动态分发 |
|
|
接口方法调用(默认动态) |
选择权 |
程序员显式选择 |
是否加 |
无选择(总是动态) |
性能 |
静态 ≈ 零开销;动态 ≈ 一次间接调用 |
同 Rust |
总是有虚调用开销 |
6.4 常用标准库 Trait#
Rust 标准库定义了大量 trait,掌握它们是写出地道 Rust 代码的关键。
6.4.1 Display 与 Debug#
use std::fmt;
struct Point {
x: f64,
y: f64,
}
// Display:面向用户的格式化
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// Debug:面向开发者的格式化(通常用 derive 自动生成)
#[derive(Debug)]
struct Rect {
top_left: Point,
bottom_right: Point,
}
6.4.2 Clone 与 Copy#
// Clone:显式深拷贝
#[derive(Clone)]
struct Config {
name: String,
values: Vec<i32>,
}
// Copy:隐式按位复制(仅适用于栈上的简单类型)
#[derive(Copy, Clone)]
struct Color {
r: u8,
g: u8,
b: u8,
}
规则:实现 Copy 的类型必须同时实现 Clone;包含堆数据(如 String、Vec)的类型不能实现 Copy。
6.4.3 From 与 Into#
struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
fn main() {
let boiling = Celsius(100.0);
let f: Fahrenheit = boiling.into(); // 自动获得 Into
println!("Water boils at {}°F", f.0);
}
6.4.4 Iterator#
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = u32; // 关联类型
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let sum: u32 = Counter::new(5)
.filter(|x| x % 2 == 0)
.sum();
println!("Sum of even numbers: {}", sum); // 2 + 4 = 6
}
6.4.5 常用 Trait 速查表#
Trait |
用途 |
常见 derive |
|---|---|---|
|
调试输出 |
✅ |
|
用户友好输出 |
❌ 需手动实现 |
|
显式深拷贝 |
✅ |
|
隐式按位复制 |
✅ |
|
相等比较 |
✅ |
|
排序比较 |
✅ |
|
哈希(用于 HashMap 的 key) |
✅ |
|
默认值 |
✅ |
|
类型转换 |
❌ 需手动实现 |
|
迭代器 |
❌ 需手动实现 |
|
析构逻辑(类似 C++ 析构函数) |
❌ 需手动实现 |
6.5 关联类型 vs 泛型参数#
6.5.1 关联类型#
trait Graph {
type Node; // 关联类型
type Edge;
fn edges(&self, node: &Self::Node) -> Vec<Self::Edge>;
}
6.5.2 泛型参数#
trait Converter<From, To> {
fn convert(&self, input: From) -> To;
}
6.5.3 何时用哪个?#
场景 |
选择 |
理由 |
|---|---|---|
一个类型只有一种实现 |
关联类型 |
|
一个类型可能有多种实现 |
泛型参数 |
|
flowchart TD
Q["一个类型对此 trait<br>是否有多种实现?"]
Q -->|否,只有一种| A["用关联类型<br>type Item = ..."]
Q -->|是,可能多种| B["用泛型参数<br>Trait<T>"]
A --> E1["例:Iterator"]
B --> E2["例:From<T>"]
style Q fill:#ffe,stroke:#333
style A fill:#bfb,stroke:#333
style B fill:#bbf,stroke:#333
6.6 Trait 对象的限制:对象安全#
并非所有 trait 都能用作 dyn Trait。能用作 trait 对象的 trait 必须是对象安全的(object safe)。
规则#
一个 trait 是对象安全的,当且仅当:
所有方法的接收者是
&self、&mut self或self: Box<Self>方法没有泛型参数
方法的返回类型不是
Self(Clone的fn clone(&self) -> Self就不行)
// ✅ 对象安全
trait Drawable {
fn draw(&self);
fn area(&self) -> f64;
}
// ❌ 不是对象安全的——返回 Self
trait Cloneable {
fn clone_self(&self) -> Self;
}
// ❌ 不是对象安全的——方法有泛型参数
trait Serializer {
fn serialize<W: std::io::Write>(&self, writer: &mut W);
}
解决方案#
如果你需要在 trait 对象中使用类似 Clone 的功能:
trait CloneBox {
fn clone_box(&self) -> Box<dyn CloneBox>;
}
impl<T: Clone + 'static> CloneBox for T {
fn clone_box(&self) -> Box<dyn CloneBox> {
Box::new(self.clone())
}
}
6.7 实战:为 Hive 项目设计插件 Trait#
让我们用本章知识为贯穿全书的 Hive 项目设计一个插件系统的 trait:
use std::any::Any;
/// 插件元数据
#[derive(Debug, Clone)]
struct PluginMeta {
name: String,
version: String,
description: String,
}
/// Hive 插件 trait
trait Plugin: Any + Send + Sync {
/// 返回插件元数据
fn meta(&self) -> PluginMeta;
/// 插件初始化
fn on_load(&mut self) -> Result<(), Box<dyn std::error::Error>>;
/// 插件卸载
fn on_unload(&mut self) -> Result<(), Box<dyn std::error::Error>>;
/// 处理事件
fn handle_event(&self, event: &str, payload: &str) -> Option<String>;
}
/// 一个示例插件:Markdown 预览
struct MarkdownPlugin {
meta: PluginMeta,
loaded: bool,
}
impl MarkdownPlugin {
fn new() -> Self {
Self {
meta: PluginMeta {
name: "markdown-preview".into(),
version: "0.1.0".into(),
description: "Render Markdown to HTML".into(),
},
loaded: false,
}
}
}
impl Plugin for MarkdownPlugin {
fn meta(&self) -> PluginMeta {
self.meta.clone()
}
fn on_load(&mut self) -> Result<(), Box<dyn std::error::Error>> {
println!("[{}] Plugin loaded", self.meta.name);
self.loaded = true;
Ok(())
}
fn on_unload(&mut self) -> Result<(), Box<dyn std::error::Error>> {
println!("[{}] Plugin unloaded", self.meta.name);
self.loaded = false;
Ok(())
}
fn handle_event(&self, event: &str, payload: &str) -> Option<String> {
match event {
"render" => {
// 简化的 Markdown → HTML 转换
let html = payload
.lines()
.map(|line| {
if line.starts_with("# ") {
format!("<h1>{}</h1>", &line[2..])
} else if line.starts_with("## ") {
format!("<h2>{}</h2>", &line[3..])
} else {
format!("<p>{}</p>", line)
}
})
.collect::<Vec<_>>()
.join("\n");
Some(html)
}
_ => None,
}
}
}
/// 插件管理器——使用 trait 对象存储异构插件
struct PluginManager {
plugins: Vec<Box<dyn Plugin>>,
}
impl PluginManager {
fn new() -> Self {
Self { plugins: Vec::new() }
}
fn register(&mut self, plugin: Box<dyn Plugin>) {
println!("Registered plugin: {}", plugin.meta().name);
self.plugins.push(plugin);
}
fn load_all(&mut self) {
for plugin in &mut self.plugins {
if let Err(e) = plugin.on_load() {
eprintln!("Failed to load {}: {}", plugin.meta().name, e);
}
}
}
fn broadcast_event(&self, event: &str, payload: &str) -> Vec<String> {
self.plugins
.iter()
.filter_map(|p| p.handle_event(event, payload))
.collect()
}
}
fn main() {
let mut manager = PluginManager::new();
manager.register(Box::new(MarkdownPlugin::new()));
manager.load_all();
let results = manager.broadcast_event("render", "# Hello\n## World\nThis is Hive.");
for html in results {
println!("{}", html);
}
}
trait Plugin: Any + Send + Sync 的意思是:任何实现 Plugin 的类型,也必须同时实现 Any、Send 和 Sync。 冒号后面的这些 trait 叫做 supertrait,可以理解为“实现插件接口的前置能力要求”,不是传统面向对象里的字段继承。
拆开看:
约束 |
含义 |
为什么插件需要它 |
|---|---|---|
|
支持运行时类型识别和向下转型 |
插件管理器有时需要把 |
|
值可以安全地移动到另一个线程 |
Tauri/Rust 桌面应用常把任务分发到后台线程 |
|
|
插件可能被多个命令、事件或窗口同时读取 |
等价地说,下面这个函数只有在 P 同时满足四个 trait 时才接受它:
fn register_plugin<P>(plugin: P)
where
P: Plugin + Any + Send + Sync,
{
// 保存到插件管理器
}
但把约束写在 trait 定义上更集中:PluginManager 只要处理 Box<dyn Plugin>,就能默认相信插件具备这些线程安全和类型识别能力。
use std::any::Any;
trait RuntimePlugin: Any + Send + Sync {
fn name(&self) -> &str;
// 常见写法:暴露 Any 引用,便于需要时做 downcast
fn as_any(&self) -> &dyn Any;
}
struct SpellCheckPlugin;
impl RuntimePlugin for SpellCheckPlugin {
fn name(&self) -> &str {
"spell-check"
}
fn as_any(&self) -> &dyn Any {
self
}
}
fn try_downcast(plugin: &dyn RuntimePlugin) {
if plugin.as_any().is::<SpellCheckPlugin>() {
println!("这是拼写检查插件");
}
}
如果某个插件内部用了不能跨线程共享的类型,例如 Rc<RefCell<T>>,它就不会满足 Send + Sync。这不是坏事,而是编译器在提醒你:这个插件不能安全地放进多线程插件系统。通常可以改用 Arc<Mutex<T>> 或 Arc<RwLock<T>>。
classDiagram
class Plugin {
<<trait>>
+meta() PluginMeta
+on_load() Result
+on_unload() Result
+handle_event(event, payload) Option~String~
}
class MarkdownPlugin {
-meta: PluginMeta
-loaded: bool
+new() Self
}
class PluginManager {
-plugins: Vec~Box~dyn Plugin~~
+register(plugin)
+load_all()
+broadcast_event(event, payload) Vec~String~
}
Plugin <|.. MarkdownPlugin : impl
PluginManager o-- Plugin : manages
6.8 本章小结#
概念 |
关键点 |
|---|---|
Trait |
定义共享行为,类似 Java interface,但可为外部类型实现 |
方法接收者 |
|
泛型 + Trait Bound |
静态分发,零成本抽象,编译时单态化 |
dyn Trait |
动态分发,通过 vtable 实现,支持异构集合 |
Supertrait |
|
关联类型 |
一个类型对 trait 只有一种实现时使用 |
对象安全 |
使用 |
标准库 Trait |
|
思考题#
为什么
Clonetrait 不是对象安全的?如果你需要克隆一个Box<dyn MyTrait>,该怎么设计?在什么场景下你会选择
dyn Trait(动态分发)而不是泛型(静态分发)?尝试为 Hive 项目添加一个
WordCountPlugin,统计给定文本的字数。
下一章预告:第七章我们将深入 Rust 的错误处理哲学——为什么 Rust 没有异常?
Result和?操作符如何让错误处理既安全又优雅?