第 5 章 Trait

Chapter 05 Trait(特征)

想象一下,你是一家公司的 HR 经理。每天你要面对各种职位描述(Job Description):会计要会做账、程序员要会写代码、设计师要会画图。但问题是,你不能因为会计会做账就让他去 debug 代码——虽然听起来像是老板会干的事。

在 Rust 的世界里,Trait 就是这个"职位描述"。它告诉编译器:“嘿,拥有这个 trait 的类型,必须能完成这些任务!“无论是打印自己、相比较大小、还是克隆一份,你都可以用 trait 来约束它们。

换句话说,trait 就是 Rust 版的"接口”(Interface),只不过它比 Java 的接口更优雅,比 Go 的 interface 更安全,比你老板的"全能型人才"要求更现实。 trait 这个词本身的意思是"特征”、“品质”——恰好说明它描述的是类型的"性格特点":这个类型能做什么?它像谁?它有什么拿手绝活?

5.1 Trait 基础

好,现在让我们正式认识一下 trait这位新朋友。它是 Rust 类型系统的灵魂,是抽象行为的基石,是让你从"写代码的"升级为"设计系统架构的"敲门砖。

5.1.1 Trait 定义

Trait 定义是整个 trait 系统的起点。你可以把它想象成一份"能力清单"——在这份清单上,你列出某种类型应该具备的所有能力(方法)。至于这个能力怎么实现,那是后面 impl 的时候才需要操心的事。这种"先定义接口,后实现细节"的思想,正是面向接口编程的精髓。

5.1.1.1 trait 声明语法(带详细解释)

声明一个 trait 就像发布一份职位招聘启事,你需要列出候选人的必备技能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 定义一个名为 Greeting 的 trait
// 翻译过来就是:"所有自称会打招呼的类型,必须实现 say_hello 方法"
trait Greeting {
    // 方法签名:返回 String,没有参数
    // 这是抽象的,不提供默认实现
    fn say_hello(&self) -> String;

    // 还可以有更多方法签名...
    fn say_goodbye(&self) -> String;
}

语法详解:

  • trait 关键字:告诉编译器"这是一个 trait 定义"
  • Greeting:trait 的名字,遵循 Rust 的命名规范(PascalCase)
  • 大括号 {} 内部:列出所有方法签名(method signature)
  • fn say_hello(&self) -> String:方法签名,类似于函数的声明,但不需要 impl 块中那样写函数体
  • &self:表示这个方法会借用 self,类似于其他语言中的 this
  • 注意 trait 内部的方法签名以分号 ; 结尾,而不是大括号 {},因为我们只声明,不定义
1
2
3
4
5
6
7
// 再来一个更贴近生活的例子:Printable trait
// 任何想要被"打印"的东西,都得实现这个 trait
trait Printable {
    // 要求实现者提供一个 format 方法
    // 返回一个 String,这个 String 应该是格式化后的表示
    fn format(&self) -> String;
}

为什么要用 trait 而不是直接写方法?

因为 trait 定义了"能力接口",而具体类型可以以不同方式实现这个能力。比如 Person 类型可能返回 “我是张三,今年18岁”,而 Dog 类型可能返回 “汪汪汪,我是旺财”。同一个 trait,不同的实现,这正是多态(polymorphism)的魅力所在。

5.1.1.2 trait 方法签名(带详细解释)

方法签名是 trait 定义中最核心的部分。它告诉编译器:“实现这个 trait 的类型,必须提供这样一个方法”。方法签名包括方法名、参数列表、返回值类型,但不包含实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 让我们定义一个更复杂的 trait,包含各种"签名风格"
trait Animal {
    // 基础版:无参数,返回 String
    fn name(&self) -> String;

    // 进阶版:带参数,返回 bool
    // 这个方法检查某种食物是否喜欢
    fn likes_food(&self, food: &str) -> bool;

    // 高阶版:可变引用 self,可以修改内部状态
    fn rename(&mut self, new_name: String);

    // 终极版:不消耗 self,但也不返回任何东西
    fn make_sound(&self);

    // 独占版:消耗 self,这个 animal 之后就不能用了
    fn into_string(self) -> String where Self: Sized;
    // where Self: Sized 是默认约束,表示 self 有确定大小
    // 大部分时候你可以省略这个约束
}

方法签名语法小课堂:

签名形式含义类比
fn foo(&self)只读借用你可以看看这只动物叫什么名字,但不能给它改名字
fn foo(&mut self)可变借用你可以给它改名字,但它的"动物身份"还是不变的
fn foo(self)消耗所有权你要把它变成字符串形式,原来的动物对象就没了
fn foo() -> T静态方法/关联函数类似于构造函数,比如 String::from("xxx")

记住这个口诀:&self 看看,&mut self 改改,self 吃掉别浪费

5.1.2 Trait 实现

定义 trait 只是画饼,实现 trait 才是真的把饼做出来。这一步你要告诉 Rust:“这个具体的类型,我要用这种方式来实现刚才定义的 trait”。就像招聘启事发出去后,终于有人来面试了,现在你要决定怎么让他展示能力。

5.1.2.1 impl Trait for Type 语法

这是 Rust 中实现 trait 的标准语法,读作"为 Type 实现 Trait"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 先定义一个 trait
trait Cookable {
    fn cook(&self) -> String;
}

// 再定义一个类型
struct Egg {
    // 鸡蛋的属性:新鲜程度,0-100
    freshness: u8,
}

// 关键语法:为 Egg 实现 Cookable trait
impl Cookable for Egg {
    // 实现具体的方法
    fn cook(&self) -> String {
        if self.freshness > 50 {
            format!("煎个荷包蛋,鲜度{}%,完美!", self.freshness)
        } else {
            format!("这蛋不太新鲜了({}%),还是做熟透了吧...", self.freshness)
        }
    }
}

fn main() {
    let my_egg = Egg { freshness: 85 };
    println!("{}", my_egg.cook());
    // 打印结果: 煎个荷包蛋,鲜度85%,完美!
}

语法分解:

  • impl:关键词,表示"我要开始实现了"
  • Cookable:要实现的 trait 名字
  • for Egg:为哪个类型实现,这里是 for Egg,翻译成"针对 Egg"
  • 大括号 {} 内部:具体的方法实现,也就是填上 trait 定义中那些"空白"

整个结构读起来就是:“impl Cookable for Egg” = “为 Egg 这个人颁发 Cookable(会做饭)证书”

再来看一个更贴近生活的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 定义一个 Describable trait
trait Describable {
    fn describe(&self) -> String;
}

// 定义几种类型
struct Water {
    temperature: f32,  // 水温,单位摄氏度
}

struct Coffee {
    strength: u8,      // 浓郁度,1-10
    caffeine_mg: u32,  // 咖啡因含量,毫克
}

// 为 Water 实现 Describable
impl Describable for Water {
    fn describe(&self) -> String {
        if self.temperature < 0.0 {
            "冰块警告!".to_string()
        } else if self.temperature < 30.0 {
            "凉白开,养生首选"
        } else if self.temperature < 60.0 {
            "温水,暖胃"
        } else {
            "热水,烫嘴预警"
        }.to_string()
    }
}

// 为 Coffee 实现 Describable
impl Describable for Coffee {
    fn describe(&self) -> String {
        format!(
            "咖啡:浓郁度{}级,咖啡因{}毫克,{}",
            self.strength,
            self.caffeine_mg,
            if self.caffeine_mg > 200 { "今晚别想睡了" } else { "还能接受" }
        )
    }
}

fn main() {
    let water = Water { temperature: 45.0 };
    let coffee = Coffee { strength: 7, caffeine_mg: 250 };

    println!("水: {}", water.describe());
    println!("咖啡: {}", coffee.describe());
    // 打印结果:
    // 水: 温水,暖胃
    // 咖啡: 咖啡因250毫克,今晚别想睡了
}

5.1.2.2 trait 作为约束(静态分发)

当你在泛型函数中使用 trait 约束时,Rust 会在编译时为每种具体类型生成专门的代码。这种方式叫做静态分发(Static Dispatch)——因为具体调用哪个实现,在编译阶段就决定了,没有运行时的查找开销。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// T: Greeting 是一个 trait 约束
// 意思是:"T 必须实现了 Greeting 这个 trait"
fn print_greeting<T: Greeting>(item: &T) {
    // 这里编译器知道 T 一定实现了 say_hello
    // 所以可以直接调用
    println!("{}", item.say_hello());
}

// 也可以用 where 语法,代码更清晰
fn print_greeting_clean<T>(item: &T)
where
    T: Greeting,
{
    println!("{}", item.say_hello());
}

// 多个约束也是可以的
fn print_multiple<T>(item: &T)
where
    T: Greeting + Printable,
    // T 必须同时实现 Greeting 和 Printable
{
    println!("{}", item.format());
    println!("{}", item.say_hello());
}

// 实现一下 Greeting trait 先
struct Robot {
    name: String,
}

impl Greeting for Robot {
    fn say_hello(&self) -> String {
        format!("01011001,你好!我是机器人{}", self.name)
    }
    fn say_goodbye(&self) -> String {
        format!("再见,人类!{}下线了", self.name)
    }
}

fn main() {
    let robot = Robot { name: "R2-D2".to_string() };
    print_greeting(&robot);
    // 打印结果: 01011001,你好!我是机器人R2-D2
}

静态分发 vs 动态分发 的区别:

静态分发就像是你去餐厅点菜,服务员说"等一下,厨师专门为你做这道菜"。菜是现做的、新鲜的、专属于你的,但需要更多时间准备(编译时间长)。

动态分发则像是你去吃自助餐,你拿了一个"通用餐盘",厨房做好了各种菜,你 runtime 的时候才决定往盘子里放哪道菜。准备快(编译快),但吃的时候要自己选(运行时查找)。

Rust 的泛型约束默认是静态分发,dyn Trait 是动态分发。

5.1.3 默认方法实现

有时候,你希望 trait 中的某些方法有一个"开箱即用"的默认实现。这样实现这个 trait 的类型如果不满意默认实现,可以自己 override;如果觉得默认实现挺不错,直接用就行。这就像公司提供的"标准工作流程"——新人可以选择遵守,也可以提出改进方案。

5.1.3.1 trait 中的默认方法实现

在 trait 定义中,你可以直接给出方法的具体实现,实现者可以"拿来就用"或者"稍作修改"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 定义一个带有默认实现的 trait
trait Discountable {
    // 这个方法没有默认实现,实现者必须自己写
    fn original_price(&self) -> f64;

    // 这个方法有默认实现!
    fn discounted_price(&self, discount: f64) -> f64 {
        // 默认逻辑:原价的 discount 折
        // 也就是说 discount=0.8 表示打八折
        self.original_price() * discount
    }

    // 再来一个默认方法:格式化输出价格
    fn price_tag(&self) -> String {
        format!("原价: {:.2}, 现价: {:.2}",
            self.original_price(),
            self.discounted_price(1.0))
    }
}

struct Product {
    name: String,
    price: f64,
}

impl Discountable for Product {
    // 必须实现 original_price
    fn original_price(&self) -> f64 {
        self.price
    }
    // discounted_price 和 price_tag 都使用默认实现
}

fn main() {
    let phone = Product {
        name: "iPhone 99".to_string(),
        price: 9999.0,
    };

    println!("{}", phone.price_tag());
    println!("七折后: {:.2}", phone.discounted_price(0.7));
    // 打印结果:
    // 原价: 9999.00, 现价: 9999.00
    // 七折后: 6999.30
}

默认方法的实现原理:

实际上,当你在 trait 中提供默认方法实现时,Rust 会在编译时把这个默认实现"复制"到每个使用默认实现的实现中。这不是真正的继承,而是代码生成。所以不存在什么"父类方法被子类继承"的问题,trait 之间是"组合"关系而非"继承"关系。

5.1.3.2 重写默认方法

如果你觉得默认实现太 low,完全可以自己写一个更炫酷的版本。这就像老板给的模板你嫌土,自己用 PPT 做了一个更专业的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
trait Greeting {
    fn say(&self) -> String;
}

// 默认实现
trait FormalGreeting {
    fn say(&self) -> String {
        "你好,阁下".to_string()  // 默认版:正式但有点古板
    }
}

// VIPS顾客
struct VIPCustomer {
    name: String,
    level: u8,  // VIP等级,1-5
}

impl FormalGreeting for VIPCustomer {
    // 重写!VIP 要享受特殊待遇
    fn say(&self) -> String {
        match self.level {
            5 => format!("尊贵的钻石会员{},欢迎回家!", self.name),
            4 => format!("亲爱的金卡会员{},好久不见!", self.name),
            _ => format!("{}先生/女士,这边请~", self.name),
        }
    }
}

struct RegularCustomer {
    name: String,
}

impl FormalGreeting for RegularCustomer {
    // 普通顾客就用默认实现吧
}

fn main() {
    let vip = VIPCustomer { name: "马爸爸".to_string(), level: 5 };
    let regular = RegularCustomer { name: "张三".to_string() };

    println!("VIP: {}", vip.say());
    println!("普通人: {}", regular.say());
    // 打印结果:
    // VIP: 尊贵的钻石会员马爸爸,欢迎回家!
    // 普通人: 你好,阁下
}

5.1.3.3 调用默认方法

有时候在你的自定义实现中,你可能想调用默认方法,然后再做一些额外的事情。这种情况下,你可以在 impl 块中使用 <Type as Trait>::method() 语法来显式调用默认实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
trait Printable {
    fn format(&self) -> String {
        // 默认实现就是简单粗暴的 Debug 格式
        format!("{:?}", self)
    }

    fn print_it(&self) {
        // 这个默认方法会调用 format()
        println!("{}", self.format());
    }
}

struct Book {
    title: String,
    pages: u32,
}

impl Printable for Book {
    // 我们重写 format,但想保留 print_it 的默认行为
    fn format(&self) -> String {
        format!("《{}》共{}页", self.title, self.pages)
    }
}

fn main() {
    let book = Book {
        title: "Rust 权威指南".to_string(),
        pages: 666,
    };

    book.print_it();  // 这里的 print_it 还是用的默认实现
    // 打印结果: 《Rust 权威指南》共666页
}

注意: 上面 print_it 调用的 self.format() 是动态分发的,会正确地调用到 Book 的实现。

5.1.4 Trait 继承

在 Rust 中,trait 可以"继承"其他 trait。这意味着如果你想让类型实现子 trait,它必须同时实现所有父 trait。这就像是你发布了一个"高级厨师"的职位描述,但其中写着"必须先有厨师资格证"——想要高级职位?先把基础要求满足了。

5.1.4.1 trait Sub: Base 语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 父 trait:基础能力
trait CanRead {
    fn read(&self) -> String;
}

// 子 trait:继承 CanRead,还要加一个写的能力
trait CanWrite: CanRead {
    fn write(&self, content: &str) -> String;
}

// 如果你想实现 CanWrite,你必须先实现 CanRead
struct Writer {
    name: String,
    articles_written: u32,
}

impl CanRead for Writer {
    fn read(&self) -> String {
        format!("{}正在阅读参考资料...", self.name)
    }
}

impl CanWrite for Writer {
    fn write(&self, content: &str) -> String {
        format!("{}正在撰写文章,内容: {}", self.name, content)
    }
}

fn main() {
    let writer = Writer {
        name: "小明".to_string(),
        articles_written: 10,
    };

    println!("{}", writer.read());
    println!("{}", writer.write("Rust真有趣!"));
    // 打印结果:
    // 小明正在阅读参考资料...
    // 小明正在撰写文章,内容: Rust真有趣!
}

语法解释:

trait CanWrite: CanRead 中的 : 意思是"继承"或"扩展"。可以理解为:

  • “CanWrite 是 CanRead 的子集”
  • “要成为 CanWrite,必须先满足 CanRead 的要求”
  • “CanWrite 在 CanRead 基础上加了自己的要求”

5.1.4.2 子 trait 的完整约束

当你在泛型中使用继承了其他 trait 的 trait 时,你需要列出完整的约束。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 继续上面的例子,加入更多层级
trait CanSpeak: CanRead {
    fn speak(&self) -> String;
}

// 顶级 trait:什么都会
trait CanTeach: CanWrite + CanSpeak {
    fn teach(&self) -> String;
}

// 普通讲师
struct Lecturer {
    name: String,
    field: String,
}

impl CanRead for Lecturer {
    fn read(&self) -> String {
        format!("{}在研究{}", self.name, self.field)
    }
}

impl CanSpeak for Lecturer {
    fn speak(&self) -> String {
        format!("{}开始讲课了!", self.name)
    }
}

impl CanWrite for Lecturer {
    fn write(&self, content: &str) -> String {
        format!("{}写了: {}", self.name, content)
    }
}

impl CanTeach for Lecturer {
    fn teach(&self) -> String {
        format!("{}开设了一门课: {}", self.name, self.field)
    }
}

// 使用时,约束要完整
fn teach_someone<T: CanTeach>(teacher: &T) {
    println!("{}", teacher.read());
    println!("{}", teacher.speak());
    println!("{}", teacher.write("教案"));
    println!("{}", teacher.teach());
}

fn main() {
    let lecturer = Lecturer {
        name: "王教授".to_string(),
        field: "Rust系统编程".to_string(),
    };
    teach_someone(&lecturer);
    // 打印结果:
    // 王教授在研究Rust系统编程
    // 王教授开始讲课了!
    // 王教授写了: 教案
    // 王教授开设了一门课: Rust系统编程
}

5.1.5 孤儿规则(Orphan Rule)

这是 Rust 中一个非常重要的概念,也是让很多人挠头的"奇怪规则"。为什么要有这个规则?它到底在限制什么?让我们一探究竟。

5.1.5.1 孤儿规则定义

孤儿规则:如果你想为某个类型实现某个 trait,那么要么这个 trait 定义在当前 crate 中,要么这个类型定义在当前 crate 中,二者至少要有其一

1
2
3
翻译成人话就是:
"你不能给别人的孩子(类型)穿别人的衣服(trait)"
"除非这个衣服是你自己做的,或者这个孩子是你亲生的"

为什么要有孤儿规则?

想象一下,如果允许为任何类型实现任何 trait,会发生什么?

我可以给 String 实现 Display,返回 “Hacked!”; 你也可以给 String 实现 Display,返回 “Hello”;

然后在某段代码里,调用 println!("{}", some_string),到底该调用哪个?编译器都不知道,Rust 引入了孤儿规则来避免这种"实现冲突"的噩梦。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 假设我们要给标准库的 Vec<String> 实现一个自定义 trait
// 因为 Vec 不是我们定义的,所以这会违反孤儿规则

// 错误示例(请不要尝试编译):
// impl MyTrait for Vec<String> { ... }
// 错误: 只能为当前 crate 中定义的类型实现外部 crate 的 trait

// 但是我们可以这样:定义一个自己的类型,然后给它实现标准库的 trait
mod my_module {
    pub struct MyVec<T>(pub Vec<T>);  // 包装一下,就是"新类型"

    // 现在我们可以为 MyVec<T> 实现标准库的 trait 了
    impl<T: std::fmt::Debug> std::fmt::Debug for MyVec<T> {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(f, "MyVec({:?})", self.0)
        }
    }
}

5.1.5.2 新类型模式(Newtype Pattern)

为了绕过孤儿规则,Rust 社区发明了一种经典技巧:新类型模式(Newtype Pattern)。就是用元组结构体包装一下别人的类型,这样就创造出了一个"名义上是你自己的类型",然后你就可以光明正大地给它实现各种 trait 了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 标准库的 String
use std::fmt;

// 创建一个新类型包装 String
struct Username(String);

// 为我们的新类型实现 Display
impl fmt::Display for Username {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[用户: {}]", self.0)
    }
}

// 还可以实现更多 trait...
impl Clone for Username {
    fn clone(&self) -> Self {
        Username(self.0.clone())
    }
}

fn main() {
    let name = Username("Rustacean".to_string());
    println!("{}", name);
    println!("克隆一个: {}", name.clone());
    // 打印结果:
    // [用户: Rustacean]
    // 克隆一个: [用户: Rustacean]
}

新类型模式的精髓:

它的原理很简单:元组结构体 struct Wrapper(T) 在名义上是一个全新的类型,和原来的 T 是不同的。虽然它内部就是 T,但在 Rust 的类型系统眼里,它是"Wrapper"不是"T"。

这就像你把一封信装进信封,信的内容没变,但信封让它变成了"邮件"这种新东西,可以贴邮票、盖邮戳——这些都是"邮件"能做的事,“裸信"做不了。

5.1.5.3 原则:两个实现至少有一个是本地的

这条规则确保了实现的唯一性。如果 traittype 都来自外部,那么就有可能和其他人冲突。Rust 强制你"要么你定义能力,要么你定义类型,至少占一样”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 场景1:为自己的类型实现标准库的 trait ✓
trait Serializable {
    fn serialize(&self) -> String;
}

struct MyConfig {
    name: String,
    version: u32,
}

impl Serializable for MyConfig {
    fn serialize(&self) -> String {
        format!("{{\"name\": \"{}\", \"version\": {}}}", self.name, self.version)
    }
}

// 场景2:为标准库类型实现自己的 trait ✓
trait Printable {
    fn print(&self);
}

impl Printable for Vec<i32> {
    fn print(&self) {
        println!("Vec<i32>: {:?}", self);
    }
}

// 场景3:为标准库类型实现标准库的 trait ✗(不允许!)
// impl Display for Vec<i32> { ... }  // 错误!

// 场景4:为外部 crate 的类型实现外部 crate 的 trait ✗(不允许!)
// 假设 weixin 和 pay 都是外部 crate
// impl pay::Payment for weixin::WeChatPay { ... }  // 错误!

fn main() {
    let config = MyConfig {
        name: "MyApp".to_string(),
        version: 1,
    };
    println!("{}", config.serialize());
    // 打印结果: {"name": "MyApp", "version": 1}

    let numbers = vec![1, 2, 3];
    numbers.print();
    // 打印结果: Vec<i32>: [1, 2, 3]
}

5.2 标准库常用 Trait

如果说 trait 是 Rust 的灵魂,那么标准库提供的这些 trait 就是 Rust 的"标配技能包"。它们让类型拥有了各种开箱即用的能力:可以打印、可以比较、可以克隆、可以迭代……就像 RPG 游戏里角色自带的被动技能。

掌握这些常用 trait,你就能更好地理解标准库的设计逻辑,也能写出更"符合人体工程学"的代码。

5.2.1 Debug Trait

Debug trait 是 Rust 标准库中最常用的 trait 之一,它让你的类型能够被 debug 打印。在开发和调试过程中,能够 print 出来看看内部状态是非常重要的技能。

5.2.1.1 Debug trait 定义

Debug trait 定义在 std::fmt 模块中,它只有一个方法 fmt,签名如下:

1
2
3
pub trait Debug {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error>;
}

这个 trait 的作用是:定义如何把一个类型的实例格式化成可读的字符串(用于调试)。和 Display 不同,Debug 的输出格式是给程序员看的,可能包含字段名、括号等结构信息。

5.2.1.2 {:?} 格式化占位符

{:?} 是 Debug 格式化的占位符。当你使用 println!("{:?}", value)dbg! 宏时,就是在调用 Debug trait 的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn main() {
    let number = 42;
    let text = "hello";
    let array = [1, 2, 3];

    // {:?} 是 Debug 格式
    println!("数字: {:?}", number);
    println!("文本: {:?}", text);
    println!("数组: {:?}", array);

    // {:#?} 是美化输出的 Debug 格式
    println!("数组(美化): {:#?}", array);
    // 打印结果:
    // 数字: 42
    // 文本: "hello"
    // 数组: [1, 2, 3]
    // 数组(美化): [
    //     1,
    //     2,
    //     3,
    // ]
}

5.2.1.3 #[derive(Debug)] 派生

Rust 提供了一个神奇的派生宏 #[derive(Debug)],它可以自动为你的类型生成 Debug trait 的实现。对于简单的 struct 和 enum,这比手写 fmt 方法省事多了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#[derive(Debug)]  // 一行顶一百行
struct Point {
    x: i32,
    y: i32,
}

#[derive(Debug)]
enum Direction {
    North,
    South,
    East,
    West,
}

#[derive(Debug)]
struct Rectangle {
    top_left: Point,
    bottom_right: Point,
}

fn main() {
    let p = Point { x: 10, y: 20 };
    let d = Direction::North;
    let rect = Rectangle {
        top_left: Point { x: 0, y: 10 },
        bottom_right: Point { x: 10, y: 0 },
    };

    println!("点: {:?}", p);
    println!("方向: {:?}", d);
    println!("矩形: {:?}", rect);

    // 打印结果:
    // 点: Point { x: 10, y: 20 }
    // 方向: North
    // 矩形: Rectangle { top_left: Point { x: 0, y: 10 }, bottom_right: Point { x: 10, y: 0 } }
}

derive 宏的神奇之处:

#[derive(Debug)] 实际上是一个过程宏,它会在编译时自动生成 impl Debug for Point { ... } 的代码。对于普通 struct,它会生成类似这样的代码:

1
2
3
4
5
6
7
8
impl Debug for Point {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        f.debug_struct("Point")
            .field("x", &self.x)
            .field("y", &self.y)
            .finish()
    }
}

想象一下如果手写这个要多久?derive 就是 Rust 给你发的福利。

5.2.1.4 Debug::fmt 方法签名详解

如果你不想用 derive,也可以自己实现 Debug trait。方法签名的参数 f: &mut Formatter 是一个格式化器,你通过调用它的各种方法来构建输出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::fmt;

// 手动实现 Debug
struct Person {
    name: String,
    age: u8,
    height: f64,
}

impl fmt::Debug for Person {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // debug_struct 创建一个结构体风格的调试输出
        // "Person" 是结构体名称
        f.debug_struct("Person")
            .field("name", &self.name)           // 字段名和值
            .field("age", &self.age)
            .field("height", &format!("{:.1}", self.height))  // 格式化一下
            .finish()                               // 结束,必须调用
    }
}

fn main() {
    let person = Person {
        name: "张三".to_string(),
        age: 28,
        height: 175.5,
    };

    println!("{:?}", person);
    // 打印结果: Person { name: "张三", age: 28, height: "175.5" }
}

Formatter 提供的辅助方法:

方法效果类比
debug_struct结构体风格Point { x: 1, y: 2 }
debug_tuple元组风格Tuple(...)
debug_list列表风格[1, 2, 3]
debug_set集合风格{1, 2, 3}
debug_map映射风格{"a": 1, "b": 2}
debug_enum枚举风格根据 variant 分发

5.2.1.5 {:?} 与 {:#?}(单行 vs 多行格式化)

{:?} 输出单行格式,适合紧凑展示;{:#?} 输出多行格式(pretty print),适合查看复杂的嵌套结构。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#[derive(Debug)]
struct Company {
    name: String,
    employees: Vec<Employee>,
}

#[derive(Debug)]
struct Employee {
    name: String,
    salary: u32,
}

fn main() {
    let company = Company {
        name: "宇宙科技".to_string(),
        employees: vec![
            Employee { name: "张三".to_string(), salary: 30000 },
            Employee { name: "李四".to_string(), salary: 35000 },
        ],
    };

    println!("单行格式:");
    println!("{:?}", company);

    println!("\n多行格式:");
    println!("{:#?}", company);

    // 打印结果:
    // 单行格式:
    // Company { name: "宇宙科技", employees: [Employee { name: "张三", salary: 30000 }, Employee { name: "李四", salary: 35000 }] }
    //
    // 多行格式:
    // Company {
    //     name: "宇宙科技",
    //     employees: [
    //         Employee {
    //             name: "张三",
    //             salary: 30000,
    //         },
    //         Employee {
    //             name: "李四",
    //             salary: 35000,
    //         },
    //     ],
    // }
}

5.2.1.6 无法派生的场景

有些类型不能直接 derive Debug,通常是因为它们包含了一些"特殊"的字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 场景1:包含没有实现 Debug 的类型
struct CannotDerive {
    name: String,           // String 实现了 Debug,可以
    // data: Rc<RefCell<...>>,  // 如果 Rc 没实现 Debug,就不能 derive
}

// 场景2:包含裸指针
// struct WithRawPointer {
    // ptr: *const i32,  // 裸指针没有实现 Debug
// }

// 场景3:枚举的某些 variant 携带非 Debug 类型
// 这时需要手动实现,为不能 Debug 的字段写个替代品

use std::fmt;

struct PartialDebug;

impl fmt::Debug for PartialDebug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<non-debuggable data>")
    }
}

#[derive(Debug)]
struct MayHaveSecrets {
    name: String,
    // secret: PartialDebug,  // 用一个实现了 Debug 的占位
}

fn main() {
    let m = MayHaveSecrets {
        name: "秘密项目".to_string(),
    };
    println!("{:?}", m);
    // 打印结果: MayHaveSecrets { name: "秘密项目" }
}

5.2.2 Display Trait

如果说 Debug 是给程序员看的,那 Display 就是给用户看的。它定义了类型的"公开形象"——如何被格式化输出。

5.2.2.1 Display trait 定义

1
2
3
pub trait Display {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error>;
}

Display 和 Debug 的签名几乎一样,区别在于:Debug 有 ?,Display 没有;Debug 面向程序员,Display 面向用户。

1
2
// Display 的签名特点:没有 ? 标记,说明不能失败
// 实际上 Result<(), Error> 在成功时总是 Ok(())

5.2.2.2 {} 格式化占位符

{} 是 Display 的占位符,这就是你最常用的 println!("{}", value)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::fmt;

struct Money {
    amount: f64,
    currency: String,
}

impl fmt::Display for Money {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 使用 write! 宏将格式化字符串写入 Formatter
        write!(f, "{:.2} {}", self.amount, self.currency)
    }
}

fn main() {
    let wallet = Money {
        amount: 1234.567,
        currency: "人民币".to_string(),
    };

    println!("余额: {}", wallet);
    // 打印结果: 余额: 1234.57 人民币
}

5.2.2.3 Display 不能直接派生

与 Debug 不同,Display 不能 derive。这是有意为之的设计决策:编译器无法猜测你希望如何向用户展示数据,所以必须手动实现。

1
2
3
4
5
6
7
8
9
// 这是行不通的:
// #[derive(Display)]  // 错误!Display 不能 derive

// 你必须手写:
// impl Display for MyType {
//     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
//         ...
//     }
// }

为什么不能 derive?因为 Display 的输出是给用户看的,而用户可能有不同的期望。比如一个分数 Rational(1, 3),你可能想显示为 “1/3”,也可能想显示为 “0.333…",编译器怎么猜?

5.2.2.4 ToString trait

ToString trait 提供了 to_string 方法,它实际上是调用了 Display trait。所以如果你已经实现了 Display,就自动获得了 to_string 的能力。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::fmt;

struct Temperature {
    celsius: f64,
}

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}°C", self.celsius)
    }
}

fn main() {
    let temp = Temperature { celsius: 36.6 };

    // to_string 内部调用了 Display
    let s = temp.to_string();
    println!("字符串: {}", s);
    println!("字符串: {:?}", s);  // 这里 s 已经是 String 了

    // 也可以用 .to_string() 直接转换
    let s2 = temp.to_string();
    // 打印结果:
    // 字符串: 36.6°C
    // 字符串: "36.6°C"
}

Display vs ToString:

特性DisplayToString
derive不能不能
目的给用户看转成 String
实现必须手写自动获得(如果实现了 Display)
方法fmt(&self, f: &mut Formatter)to_string(&self) -> String

5.2.3 Clone Trait

Clone 是 Rust 中用于创建"完整副本"的 trait。如果你需要复制一个值的所有权,而不只是借用,Clone 就是你的好帮手。

5.2.3.1 Clone trait 定义

1
2
3
pub trait Clone {
    fn clone(&self) -> Self;
}

就这么简单!clone 方法返回一个 Self 类型的值,这个值应该是原值的完整拷贝。

5.2.3.2 #[derive(Clone)] 派生

对于简单类型(所有字段都实现了 Clone),可以直接 derive。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Clone, Debug)]
struct Book {
    title: String,        // String 实现了 Clone
    author: String,       // String 实现了 Clone
    pages: u32,          // u32 实现了 Clone
}

fn main() {
    let book1 = Book {
        title: "Rust 实战".to_string(),
        author: "老王".to_string(),
        pages: 520,
    };

    // clone 一个副本
    let book2 = book1.clone();

    println!("原件: {:?}", book1);  // book1 还能用!
    println!("克隆: {:?}", book2);
    // 打印结果:
    // 原件: Book { title: "Rust 实战", author: "老王", pages: 520 }
    // 克隆: Book { title: "Rust 实战", author: "老王", pages: 520 }
}

5.2.3.3 深拷贝语义

Clone 实现的是深拷贝。如果你的 struct 包含堆分配的数据(如 String、Vec),clone 会复制这些数据,而不是只复制引用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#[derive(Clone, Debug)]
struct Classroom {
    name: String,          // 堆上的字符串
    students: Vec<String>, // 堆上的动态数组
}

fn main() {
    let class1 = Classroom {
        name: "CS101".to_string(),
        students: vec!["张三".to_string(), "李四".to_string()],
    };

    let class2 = class1.clone();

    // 修改克隆后的对象,不会影响原件
    // (如果是引用拷贝/浅拷贝,就会互相影响)

    println!("原件: {:?}", class1);
    println!("克隆: {:?}", class2);

    // 打印结果:
    // 原件: Classroom { name: "CS101", students: ["张三", "李四"] }
    // 克隆: Classroom { name: "CS101", students: ["张三", "李四"] }
}

Clone vs Copy:

特性CloneCopy
推导方式必须 derive 或手动实现编译器自动识别
代价可能较重(深拷贝)很轻(按位复制)
调用方式必须显式调用 .clone()隐式发生
例子String, Vec, Box<T>i32, bool, char

5.2.3.4 clone 方法

clone 方法是 Clone trait 的核心,使用时注意所有权问题:clone 会复制数据,所以原件仍然归你使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Clone, Debug)]
struct Password {
    plaintext: String,  // 假设这是敏感信息
    hash: String,
}

fn main() {
    let original = Password {
        plaintext: "123456".to_string(),
        hash: "e10adc3949ba59abbe56e057f20f883e".to_string(),
    };

    // clone 后原件还能用
    let backup = original.clone();

    // 但你有两个独立的 Password 对象了
    println!("原件: {:?}", original);
    println!("备份: {:?}", backup);

    // 打印结果:
    // 原件: Password { plaintext: "123456", hash: "e10adc3949ba59abbe56e057f20f883e" }
    // 备份: Password { plaintext: "123456", hash: "e10adc3949ba59abbe56e057f20f883e" }
}

5.2.4 Copy Trait

Copy 是 Rust 中一个"神奇"的 trait —— 它没有方法,只是一个标记 trait(marker trait)。实现了 Copy 的类型,在赋值时会发生"按位复制”,不需要显式调用任何方法,这种复制对用户来说是完全隐式的。

5.2.4.1 Copy trait 定义

1
2
3
pub trait Copy: Clone {}
// Copy 继承自 Clone
// 意思是:如果你想让你的类型是 Copy,它必须也是 Clone

Copy 没有任何方法,它只是一个标记,告诉编译器:“这个类型可以在赋值时按位复制”。

5.2.4.2 Copy 是 Clone 的子集

这条规则 Copy: Clone 意味着:所有 Copy 的类型都必须是 Clone 的。这是合理的 —— 如果你的类型可以隐式复制,那它当然也可以显式复制(clone)。

1
2
3
4
5
6
类比理解:
- Copy 是"复印机":一键自动复印,你感觉不到发生了什么
- Clone 是"手动复印":你需要按复印键,而且可能要等久一点

如果一个类型可以"一键复印"(Copy),它当然也可以"手动复印"(Clone)。
但反过来不一定对:手动复印可以做到的事情(深拷贝复杂结构),一键复印可能做不到。

5.2.4.3 哪些类型可以 Copy

Rust 规定:只有那些"不需要堆分配、可以按位复制的简单类型"才能是 Copy。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
✓ 可以 Copy 的类型:
  - 所有原始类型:i32, u64, f64, bool, char
  - 只包含 Copy 类型的数组和元组
  - 不可变借用 &T(只要 T 是 Copy)
  - 固定大小的数组(元素是 Copy)

✗ 不能 Copy 的类型:
  - String(堆分配)
  - Vec(堆分配)
  - Box<T>(堆分配)
  - 所有自定义 struct/enum(除非显式标记)
  - 闭包(可能捕获环境)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn main() {
    // 原始类型都是 Copy
    let a: i32 = 42;
    let b = a;  // 隐式复制,没有 move
    println!("a = {}, b = {}", a, b);  // a 还能用!

    let c: f64 = 3.14;
    let d = c;
    println!("c = {}, d = {}", c, d);  // c 还能用!

    // 数组(元素是 Copy)是 Copy
    let arr1 = [1, 2, 3];
    let arr2 = arr1;
    println!("arr1 = {:?}", arr1);  // arr1 还能用!

    // 元组(元素都是 Copy)是 Copy
    let tuple1 = (1, 2.0, 'a');
    let tuple2 = tuple1;
    println!("tuple1 = {:?}", tuple1);  // tuple1 还能用!

    // 打印结果:
    // a = 42, b = 42
    // c = 3.14, d = 3.14
    // arr1 = [1, 2, 3]
    // tuple1 = (1, 2.0, 'a')
}

5.2.4.4 #[derive(Copy, Clone)]

对于自定义类型,如果所有字段都是 Copy 的,你可以 derive 这两个 trait。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#[derive(Copy, Clone, Debug)]
struct Point {
    x: i32,
    y: i32,
}

#[derive(Copy, Clone, Debug)]
struct Color {
    r: u8,
    g: u8,
    b: u8,
}

#[derive(Copy, Clone, Debug)]
struct Pixel(Point, Color);  // 元组结构体,字段都是 Copy

fn main() {
    let p = Point { x: 10, y: 20 };
    let p2 = p;  // Copy,p 还能用
    println!("p = {:?}", p);
    println!("p2 = {:?}", p2);

    let px = Pixel(Point { x: 0, y: 0 }, Color { r: 255, g: 0, b: 0 });
    let px2 = px;  // Copy,px 还能用
    println!("px = {:?}", px);
    println!("px2 = {:?}", px2);

    // 打印结果:
    // p = Point { x: 10, y: 20 }
    // p2 = Point { x: 10, y: 20 }
    // px = Pixel(Point { x: 0, y: 0 }, Color { r: 255, g: 0, b: 0 })
    // px2 = Pixel(Point { x: 0, y: 0 }, Color { r: 255, g: 0, b: 0 })
}

5.2.4.5 Copy 的隐含语义

Copy 的"隐式复制"发生在:

  • 赋值:let b = a;
  • 函数传参:foo(a) (按值传递)
  • 返回值:return a;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Copy, Clone, Debug)]
struct Metric {
    value: f64,
}

fn double(x: Metric) -> Metric {
    Metric { value: x.value * 2.0 }
}

fn main() {
    let m = Metric { value: 42.0 };

    // 按值传参,m 被复制
    let result = double(m);

    // m 还在!因为 Metric 是 Copy
    println!("原始: {:?}", m);
    println!("翻倍: {:?}", result);

    // 打印结果:
    // 原始: Metric { value: 42.0 }
    // 翻倍: Metric { value: 84.0 }
}

为什么 Rust 要区分 Copy 和 Clone?

这是 Rust 追求明确性性能的结果:

  1. 明确性:Clone 需要显式调用 .clone(),程序员知道这可能是个昂贵的操作
  2. 性能:Copy 类型按位复制,非常快,隐式发生也不影响性能
  3. 安全:Rust 的所有权系统要求你对"复制"行为有清晰的认识

简单说:Copy 是给"简单便宜的类型"准备的,Clone 是给"可能很贵的类型"准备的。

5.2.5 Drop Trait

Drop trait 让你在值离开作用域前执行一些清理工作,比如释放资源、关闭文件、打印日志等。Rust 的 RAII(Resource Acquisition Is Initialization)模式就依赖于 Drop。

5.2.5.1 Drop trait 定义

1
2
3
pub trait Drop {
    fn drop(&mut self);
}

只有这一个方法!当值离开作用域时,Rust 自动调用这个方法。

5.2.5.2 drop 方法

drop 方法是你"自定义清理逻辑"的地方。在这个方法里,你可以做任何需要"收尾"的事情。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Connection {
    ip: String,
    port: u16,
}

impl Drop for Connection {
    fn drop(&mut self) {
        // 当 Connection 离开作用域时,自动调用这里
        println!("关闭连接: {}:{}", self.ip, self.port);
    }
}

fn main() {
    let conn = Connection {
        ip: "192.168.1.1".to_string(),
        port: 8080,
    };

    println!("建立连接: {}:{}", conn.ip, conn.port);
    println!("使用连接...");

    // conn 在这里离开作用域,drop 被自动调用
    // 打印结果:
    // 建立连接: 192.168.1.1:8080
    // 使用连接...
    // 关闭连接: 192.168.1.1:8080
}

5.2.5.3 mem::drop 显式调用

有时候你想提前释放资源,而不是等作用域结束。Rust 提供了 std::mem::drop 函数来做这件事。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use std::mem::drop;

struct File {
    name: String,
}

impl Drop for File {
    fn drop(&mut self) {
        println!("文件 {} 被关闭", self.name);
    }
}

fn main() {
    let file = File { name: "data.txt".to_string() };
    println!("打开文件: {}", file.name);

    // 显式调用 drop,提前释放
    drop(file);

    println!("干点别的事情...");

    // 注意:file 已经不可用了,因为我们显式 drop 了
    // 打印结果:
    // 打开文件: data.txt
    // 文件 data.txt 被关闭
    // 干点别的事情...
}

drop 函数的原理:

std::mem::drop 实际上就是调用了值的 drop 方法:

1
2
3
pub fn drop<T>(x: T) {
    // x 在这里离开作用域,Drop::drop 被调用
}

所以 drop(x)x.drop()(如果 Drop 有 pub 方法的话)效果是一样的。但 drop() 函数更安全,因为它确保了 drop 一定会发生。

5.2.5.4 Drop 与 Copy 的互斥

这是一个有趣的规则:不能同时为同一个类型实现 Copy 和 Drop。为什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
原因分析:
- Copy 的语义是"按位复制,之后两个值独立存在"
- Drop 的语义是"清理资源"

如果一个类型既是 Copy 又是 Drop:
1. 赋值时发生 Copy,比如 let b = a;
2. a 和 b 都存在
3. a 离开作用域,调用 Drop
4. b 也要离开作用域,再调用 Drop
5. 资源被释放两次!(use-after-free bug)

所以 Rust 禁止了这种组合。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 这个代码无法编译:
// #[derive(Copy, Clone)]
// struct BadExample {
//     data: String,  // String 不是 Copy,所以其实不会冲突
// }

// 但如果你试图让一个包含非 Copy 字段的类型变成 Copy,
// 编译器会拒绝:
// error: the trait `Copy` cannot be derived for structs containing non-`Copy` types

// Rust 的设计让你不会犯这个错误。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 正确的做法:选择 Clone 或 Drop
// 方案1:只要 Clone,不要 Copy
#[derive(Clone)]
struct CloneOnly {
    data: String,
}

// 方案2:Clone 和 Drop 都要
struct BothNeeded {
    resource: Vec<u8>,
}

impl Drop for BothNeeded {
    fn drop(&mut self) {
        println!("释放资源...");
    }
}

fn main() {
    let c = CloneOnly { data: "hello".to_string() };
    let c2 = c.clone();  // 显式克隆
    println!("c = {}, c2 = {}", c.data, c2.data);
}

5.2.6 Default Trait

Default trait 提供了一个创建"默认值"的方式。这在你需要初始化一个对象但又不想指定每个字段时很有用,比如和 struct update 语法搭配。

5.2.6.1 Default trait 定义

1
2
3
pub trait Default {
    fn default() -> Self;
}

这就是全部了!返回一个 Self 类型的默认值。

5.2.6.2 #[derive(Default)] 派生

对于所有字段都实现了 Default 的类型,可以直接 derive。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#[derive(Default, Debug)]
struct Config {
    host: String,        // String 默认是空字符串
    port: u16,           // u16 默认是 0
    max_connections: usize,  // usize 默认是 0
    debug_mode: bool,   // bool 默认是 false
}

fn main() {
    let default_config = Config::default();
    println!("默认配置: {:?}", default_config);
    // 打印结果: 默认配置: Config { host: "", port: 0, max_connections: 0, debug_mode: false }
}

5.2.6.3 Default::default() 用法

有两种使用方式:直接调用 Type::default() 或者使用 ..Default::default()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[derive(Default, Debug)]
struct Player {
    name: String,
    health: u32,
    level: u8,
    score: u64,
}

fn main() {
    // 方式1:直接调用
    let default_player = Player::default();
    println!("默认玩家: {:?}", default_player);

    // 方式2:struct update 语法,部分字段覆盖
    let hero = Player {
        name: "超级玛丽".to_string(),
        health: 100,
        ..Default::default()  // 其他字段用默认值
    };
    println!("英雄玩家: {:?}", hero);

    // 打印结果:
    // 默认玩家: Player { name: "", health: 0, level: 0, score: 0 }
    // 英雄玩家: Player { name: "超级玛丽", health: 100, level: 0, score: 0 }
}

5.2.6.4 struct Update 模式

struct update 语法 ..Default::default() 是 Default trait 最常用的场景之一。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#[derive(Default, Debug)]
struct ServerConfig {
    ip: String,
    port: u16,
    workers: u32,
    timeout_secs: u64,
}

fn main() {
    // 开发环境配置
    let dev_config = ServerConfig {
        ip: "127.0.0.1".to_string(),
        port: 8080,
        ..Default::default()
    };

    // 生产环境配置
    let prod_config = ServerConfig {
        ip: "0.0.0.0".to_string(),
        port: 80,
        workers: 16,
        timeout_secs: 300,
        ..Default::default()
    };

    println!("开发环境: {:?}", dev_config);
    println!("生产环境: {:?}", prod_config);

    // 打印结果:
    // 开发环境: ServerConfig { ip: "127.0.0.1", port: 8080, workers: 0, timeout_secs: 0 }
    // 生产环境: ServerConfig { ip: "0.0.0.0", port: 80, workers: 16, timeout_secs: 300 }
}

5.2.7 From Trait

From trait 定义了类型之间的转换关系。如果你能从类型 A 转换成类型 B,那么为 A 实现 From<B> 或者为 B 实现 From<A> 即可。

5.2.7.1 From trait 定义

1
2
3
pub trait From<T> {
    fn from(value: T) -> Self;
}

From<T> 表示"可以从 T 创建 Self"。

5.2.7.2 From 实现举例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 1. 从 String 到 String(其实不需要,但可以展示)
impl From<String> for String {
    fn from(s: String) -> String {
        s
    }
}

// 2. 从 &str 到 String
impl From<&str> for String {
    fn from(s: &str) -> String {
        String::from(s)
    }
}

// 3. 从 i32 到 f64
impl From<i32> for f64 {
    fn from(n: i32) -> f64 {
        n as f64
    }
}

// 4. 从数组到 Vec
impl From<[i32; 3]> for Vec<i32> {
    fn from(arr: [i32; 3]) -> Vec<i32> {
        vec![arr[0], arr[1], arr[2]]
    }
}

fn main() {
    // 使用 .into() 方法,Rust 会自动推断使用哪个 From 实现
    let s1: String = String::from("hello");
    let s2: String = "world".into();
    let n: f64 = 42.into();
    let v: Vec<i32> = [1, 2, 3].into();

    println!("s1 = {}", s1);
    println!("s2 = {}", s2);
    println!("n = {}", n);
    println!("v = {:?}", v);

    // 打印结果:
    // s1 = hello
    // s2 = world
    // n = 42
    // v = [1, 2, 3]
}

5.2.7.3 From 与 Into 的关系

FromInto 是同一个硬币的两面。如果你实现了 From<T>,你就自动获得了 Into<T> 的能力。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fn main() {
    // Into 是 From 的反函数
    // 如果 T: From<U>,那么 U: Into<T>

    let s: String = "hello".into();  // 相当于 String::from("hello")
    // 或者显式写:
    let s2: String = Into::<String>::into("hello");

    println!("s = {}, s2 = {}", s, s2);

    // 什么时候用 From?什么时候用 Into?
    // - 写库/类型定义时:实现 From,因为 Into 是自动获得的
    // - 调用方代码时:用 .into() 更简洁
    // 打印结果:
    // s = hello, s2 = hello
}

实现 From 的规则:

  1. 互反性:如果你为 A 实现了 From<B>,那你可能也想为 B 实现 From<A>,但这不是强制的
  2. 单向转换:From 定义的是"单向转换",不要假设它是对称的
  3. 正确性:转换应该保持语义,比如把"10"(字符串)转换成 10(数字)是合理的,但把 “hello” 转换成 0 就不太对

5.2.8 Into Trait

Into 是 From 的"镜像",它让你可以用 value.into() 的方式转换类型。

5.2.8.1 Into trait 定义

1
2
3
pub trait Into<T> {
    fn into(self) -> T;
}

实际上在标准库中,Into 的完整定义更复杂(包含泛型约束),但本质就是这样。

1
2
3
// 标准库中 Into 的真实签名是:
// impl<T> Into<T> for U where U: From<T> { ... }
// 所以 Into 的实现是由 From 自动派生的

5.2.8.2 T: Into 约束

在泛型函数中,你可以通过 T: Into<U> 来约束参数类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 这个函数接受任何可以转换成 String 的类型
fn to_string<S: Into<String>>(value: S) -> String {
    value.into()
}

// 更灵活的版本,返回类型由调用方指定
fn convert<T, U>(value: T) -> U
where
    T: Into<U>,
{
    value.into()
}

fn main() {
    // 各种类型转 String
    println!("{}", to_string("hello"));
    println!("{}", to_string(String::from("world")));
    println!("{}", to_string(123.to_string()));

    // 通用转换
    let s: String = convert("你好");
    let i: i32 = convert(3.14_f64);  // f64 到 i32 会截断
    println!("s = {}, i = {}", s, i);

    // 打印结果:
    // hello
    // world
    // 123
    // s = 你好, i = 3
}

5.2.8.3 与 From 的选用原则

1
2
3
4
5
6
7
8
9
选 From:
- 在你的类型上定义转换逻辑
- 需要明确指定转换的源类型

选 Into:
- 在调用方代码中使用
- 想让函数接受更灵活的类型

简单记忆:定义时用 From,调用时用 Into。

5.2.9 AsRef / AsMut Trait

AsRef 和 AsMut 提供了"廉价借用转换"的抽象。它们是 Rust 用来处理"可以转换为什么引用"的 trait。

5.2.9.1 AsRef trait 定义

1
2
3
pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}

AsRef<T> 意味着"可以借用成 T 的引用"。注意 ?Sized 约束允许 T 是动态大小的类型(如 str)。

5.2.9.2 AsMut trait 定义

1
2
3
pub trait AsMut<T: ?Sized> {
    fn as_mut(&mut self) -> &mut T;
}

可变版本的 AsRef,提供可变借用转换。

5.2.9.3 通用实现

标准库为很多类型提供了 AsRef 的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn main() {
    // String 实现了 AsRef<str>
    let s = String::from("hello");
    let s_ref: &str = s.as_ref();
    println!("s_ref = {}", s_ref);

    // Vec<T> 实现了 AsRef<[T]>
    let v = vec![1, 2, 3];
    let slice: &[i32] = v.as_ref();
    println!("slice = {:?}", slice);

    // String 也实现了 AsRef<[u8]> (即 bytes)
    let bytes: &[u8] = s.as_ref();
    println!("bytes = {:?}", bytes);

    // 数组也实现了 AsRef<[T; N]>
    let arr = [1, 2, 3];
    let slice: &[i32] = arr.as_ref();
    println!("arr as slice = {:?}", slice);

    // 打印结果:
    // s_ref = hello
    // slice = [1, 2, 3]
    // bytes = [104, 101, 108, 108, 111]
    // arr as slice = [1, 2, 3]
}

5.2.9.4 AsRef 作为泛型约束

AsRef 常被用作泛型约束,让函数接受多种可以转换成引用的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 这个函数可以接受任何可以转换成 &str 的类型
fn print_if_valid(input: impl AsRef<str>) {
    let s = input.as_ref();
    if !s.is_empty() {
        println!("有效输入: {}", s);
    }
}

fn main() {
    print_if_valid("hello");           // &str
    print_if_valid(String::from("world"));  // String
    print_if_valid("你好".to_string());     // String again

    // 打印结果:
    // 有效输入: hello
    // 有效输入: world
    // 有效输入: 你好
}

AsRef vs From/Into:

Trait转换代价返回类型用途
AsRef无代价(只是借用)&T通用引用接受
From/Into可能需要分配/复制T值转换

简单说:AsRef 不拿走所有权,只是借个参考;From/Into 可能要搬东西。

5.2.10 Borrow / BorrowMut Trait

Borrow 和 BorrowMut 类似于 AsRef/AsMut,但它们额外承诺了哈希稳定性——同一个值 Borrow 出来的引用,在哈希时会得到相同的结果。这对 HashMap 的键来说非常重要。

5.2.10.1 Borrow trait 定义

1
2
3
pub trait Borrow<Borrowed> {
    fn borrow(&self) -> &Borrowed;
}

Borrow 返回一个借用,但比 AsRef 更严格:它要求"借用的值在哈希时等于原值"。

5.2.10.2 BorrowMut trait 定义

1
2
3
pub trait BorrowMut<Borrowed>: Borrow<Borrowed> {
    fn borrow_mut(&mut self) -> &mut Borrowed;
}

可变借用版本,也继承自 Borrow。

5.2.10.3 HashMap 的键约束

当你用 HashMap<K, V> 时,K 必须实现 Borrow<Q>,这样你才能用 Q 类型的值来查找 K 类型的键。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::collections::HashMap;

fn main() {
    let mut scores: HashMap<String, u32> = HashMap::new();

    scores.insert(String::from("Alice"), 100);
    scores.insert(String::from("Bob"), 85);
    scores.insert(String::from("Charlie"), 92);

    // 用 &str 查找 String 键!这就是 Borrow 的威力
    // 因为 String 实现了 Borrow<str>
    if let Some(&score) = scores.get("Alice") {
        println!("Alice 的分数: {}", score);
    }

    // 如果没有 Borrow,你得这样:
    // scores.get(&String::from("Alice"))
    // 有了 Borrow,直接用字符串字面量就行

    // 打印结果:
    // Alice 的分数: 100
}

5.2.10.4 Borrow vs AsRef

1
2
3
4
5
6
7
8
关键区别:
- AsRef:只要求"可以转换",不保证哈希等价
- Borrow:要求"可以转换,并且哈希等价"(对 HashMap 键查找很重要)

举例:
- String 实现了 AsRef<str> 和 AsRef<[u8]>
- String 也实现了 Borrow<str> 和 Borrow<[u8]>(保证哈希等价)
- 但注意:AsRef<[u8]> 和 Borrow<[u8]> 是不同的约束,适用于不同场景
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;

fn hash<T: Hash>(value: &T) -> u64 {
    let mut hasher = DefaultHasher::new();
    value.hash(&mut hasher);
    hasher.finish()
}

fn main() {
    let s = String::from("hello");
    let s_str: &str = s.borrow();

    println!("String hash: {}", hash(&s));
    println!("str hash: {}", hash(s_str));
    println!("相等: {}", hash(&s) == hash(s_str));
    // Borrow 保证哈希等价

    // 打印结果:
    // String hash: 9835680345717941425
    // str hash: 9835680345717941425
    // 相等: true
}

5.2.11 Eq / PartialEq Trait

相等性比较是 Rust 中最常用的操作之一。PartialEq 允许"部分相等"(可以有 NaN 这样的情况),Eq 则是"完全相等"。

5.2.11.1 PartialEq trait 定义

1
2
3
4
pub trait PartialEq<Rhs: ?Sized = Self> {
    fn eq(&self, other: &Rhs) -> bool;
    fn ne(&self, other: &Rhs) -> bool { !self.eq(other) }
}

PartialEq 的"Partial"来自于数学上的"偏序关系"——它不要求自反性(a == a 在浮点数中不一定成立)。

5.2.11.2 #[derive(PartialEq)] 派生

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#[derive(PartialEq, Debug)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

#[derive(PartialEq)]
enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    let v1 = Version { major: 1, minor: 2, patch: 3 };
    let v2 = Version { major: 1, minor: 2, patch: 3 };
    let v3 = Version { major: 1, minor: 2, patch: 4 };

    println!("v1 == v2: {}", v1 == v2);  // true
    println!("v1 == v3: {}", v1 == v3);  // false

    let d1 = Direction::North;
    let d2 = Direction::South;
    println!("North == North: {}", d1 == d1);  // true
    println!("North == South: {}", d1 == d2);  // false

    // 打印结果:
    // v1 == v2: true
    // v1 == v3: false
    // North == North: true
    // North == South: false
}

5.2.11.3 Eq trait

Eq 是一个标记 trait,表示"完全相等"。

1
2
pub trait Eq: PartialEq<Self> {}
// 没有任何额外方法,只是标记

Eq 要求 PartialEq 满足:

  • 自反性:a == a 总是 true
  • 对称性:a == b 意味着 b == a
  • 传递性:a == b && b == c 意味着 a == c

5.2.11.4 f32 / f64 不是 Eq 的原因

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
    // 浮点数的 NaN 是著名的"不等于自己"
    let nan = f64::NAN;
    println!("NaN == NaN: {}", nan == nan);  // false!
    println!("NaN.is_nan(): {}", nan.is_nan());  // true

    // 所以 f64 只能实现 PartialEq,不能实现 Eq
    // 因为 Eq 要求自反性(a == a 必须为 true)

    // 其他 NaN 相关的坑:
    println!("0.0 == -0.0: {}", 0.0_f64 == -0.0_f64);  // true
    println!("1.0 / 0.0: {}", 1.0 / 0.0);  // inf
    println!("-1.0 / 0.0: {}", -1.0 / 0.0);  // -inf

    // 打印结果:
    // NaN == NaN: false
    // NaN.is_nan(): true
    // 0.0 == -0.0: true
    // 1.0 / 0.0: inf
    // -1.0 / 0.0: -inf
}

5.2.12 Ord / PartialOrd Trait

排序 trait 让你可以对值进行比较。和相等性一样,排序也分"部分"和"完全"。

5.2.12.1 PartialOrd trait 定义

1
2
3
4
5
6
7
pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {
    fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
    fn lt(&self, other: &Rhs) -> bool { ... }
    fn le(&self, other: &Rhs) -> bool { ... }
    fn gt(&self, other: &Rhs) -> bool { ... }
    fn ge(&self, other: &Rhs) -> bool { ... }
}

partial_cmp 返回 Option<Ordering>,因为有些值可能无法比较(如 NaN)。

5.2.12.2 Ordering 枚举

1
2
3
4
5
pub enum Ordering {
    Less,       // self < other
    Equal,      // self == other
    Greater,    // self > other
}

Ordering 是比较结果的枚举,用于精确表达"谁大谁小"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn compare<T: Ord>(a: &T, b: &T) -> Ordering {
    a.cmp(b)
}

fn main() {
    let x = 10;
    let y = 20;

    println!("x.cmp(&y) = {:?}", x.cmp(&y));  // Less
    println!("y.cmp(&x) = {:?}", y.cmp(&x));  // Greater
    println!("x.cmp(&x) = {:?}", x.cmp(&x));  // Equal

    // 也可以用 matches 检查
    println!("x < y: {}", matches!(x.cmp(&y), Ordering::Less));
    println!("x > y: {}", matches!(x.cmp(&y), Ordering::Greater));

    // 打印结果:
    // x.cmp(&y) = Less
    // y.cmp(&x) = Greater
    // x.cmp(&x) = Equal
    // x < y: true
    // x > y: false
}

5.2.12.3 #[derive(PartialOrd)] 派生

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[derive(PartialOrd, PartialEq, Debug)]
struct Product {
    name: String,
    price: f64,
}

fn main() {
    let p1 = Product { name: "苹果".to_string(), price: 5.0 };
    let p2 = Product { name: "香蕉".to_string(), price: 3.0 };
    let p3 = Product { name: "樱桃".to_string(), price: 50.0 };

    // 按价格排序
    println!("p1 < p2: {}", p1 < p2);  // 5.0 < 3.0? false
    println!("p2 < p3: {}", p2 < p3);  // 3.0 < 50.0? true

    // derive 的 PartialOrd 按字典序比较字段
    // 先比较 name,如果相等再比较 price
    let p4 = Product { name: "苹果".to_string(), price: 10.0 };
    println!("p1 < p4: {}", p1 < p4);  // true (price 更小)

    // 打印结果:
    // p1 < p2: false
    // p2 < p3: true
    // p1 < p4: true
}

5.2.12.4 Ord trait

Ord 要求完全排序,定义如下:

1
2
3
pub trait Ord: Eq + PartialOrd<Self> {
    fn cmp(&self, other: &Self) -> Ordering;
}

Ord 和 PartialOrd 的区别:

  • PartialOrd 返回 Option<Ordering>(因为可能无法比较)
  • Ord 返回 Ordering(一定可以比较)

5.2.12.5 Ord 的完全性要求

1
2
3
4
5
6
Ord 要求:
1. 自反:cmp(a, a) == Equal
2. 反对称:cmp(a, b) == Less 当且仅当 cmp(b, a) == Greater
3. 传递:cmp(a, b) == Less && cmp(b, c) == Less 意味着 cmp(a, c) == Less

这些条件保证了一个"完全有序"的状态。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#[derive(Eq, PartialEq, Ord, Debug)]
struct Student {
    name: String,
    score: u32,
}

fn main() {
    let mut students = vec![
        Student { name: "张三".to_string(), score: 85 },
        Student { name: "李四".to_string(), score: 92 },
        Student { name: "王五".to_string(), score: 78 },
        Student { name: "赵六".to_string(), score: 92 },
    ];

    // 排序(按 score 降序,score 相同时按 name 升序)
    students.sort();

    for s in &students {
        println!("{:?}", s);
    }

    // 打印结果:
    // Student { name: "王五", score: 78 }
    // Student { name: "张三", score: 85 }
    // Student { name: "李四", score: 92 }
    // Student { name: "赵六", score: 92 }
}

5.2.13 Hash Trait

Hash trait 允许你把值哈希成一个字节序列,用于 HashMap、HashSet 等哈希数据结构的键。

5.2.13.1 Hash trait 定义

1
2
3
4
5
pub trait Hash {
    fn hash<H: Hasher>(&self, state: &mut H);
    fn hash_slice<H: Hasher>(data: &[Self], state: &mut H)
    where Self: Sized { ... }
}

核心是 hash 方法,它把数据写入一个 Hasher

5.2.13.2 #[derive(Hash)] 派生

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#[derive(Hash, Eq, PartialEq, Debug)]
struct CacheKey {
    namespace: String,
    key: String,
}

fn main() {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::Hash;

    let key1 = CacheKey {
        namespace: "users".to_string(),
        key: "12345".to_string(),
    };

    let key2 = CacheKey {
        namespace: "users".to_string(),
        key: "12345".to_string(),
    };

    let key3 = CacheKey {
        namespace: "users".to_string(),
        key: "67890".to_string(),
    };

    // 计算哈希值
    let mut hasher = DefaultHasher::new();
    key1.hash(&mut hasher);
    let hash1 = hasher.finish();

    let mut hasher2 = DefaultHasher::new();
    key2.hash(&mut hasher2);
    let hash2 = hasher2.finish();

    let mut hasher3 = DefaultHasher::new();
    key3.hash(&mut hasher3);
    let hash3 = hasher3.finish();

    println!("key1 hash: {}", hash1);
    println!("key2 hash: {}", hash2);
    println!("key3 hash: {}", hash3);
    println!("key1 == key2: {}", key1 == key2);
    println!("key1 == key3: {}", key1 == key3);

    // 打印结果:
    // key1 hash: <哈希值>
    // key2 hash: <相同的哈希值>
    // key3 hash: <不同的哈希值>
    // key1 == key2: true
    // key1 == key3: false
}

5.2.13.3 Hash 与 Eq 的协同要求

这是一个重要规则:如果 a == b,则 hash(a) == hash(b)

1
2
3
4
5
6
7
为什么这个规则很重要?
- HashMap 用哈希值来快速查找
- 如果两个相等的值哈希值不同,HashMap 就找不到它
- 这就是"哈希碰撞",会导致 bug

Rust 编译器会警告你如果 derive 了 Hash 但没有 derive Eq:
"you should implement Eq if Hash is implemented"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::collections::HashMap;
use std::hash::Hash;

#[derive(Hash, Eq, PartialEq, Debug)]
struct Username(String);

fn main() {
    let mut map: HashMap<Username, u32> = HashMap::new();

    map.insert(Username("alice".to_string()), 100);
    map.insert(Username("bob".to_string()), 85);

    // 查找,Username 必须 Hash 和 Eq 都实现
    let alice_key = Username("alice".to_string());
    if let Some(&score) = map.get(&alice_key) {
        println!("Alice 的分数: {}", score);
    }

    // 打印结果:
    // Alice 的分数: 100
}

5.2.14 ToOwned Trait

ToOwned 提供了从借用数据创建有所有权数据的能力,是 Clone 的更通用版本。

5.2.14.1 ToOwned trait 定义

1
2
3
4
5
6
7
pub trait ToOwned {
    type Owned: Borrow<Self>;

    fn to_owned(&self) -> Self::Owned;

    fn clone_into(&self, target: &mut Self::Owned) { ... }
}

ToOwned 允许你从 &T 创建 T 的所有者版本。

5.2.14.2 to_owned vs clone

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn main() {
    // &str 到 String
    let s: &str = "hello";
    let owned_str: String = s.to_owned();  // ToOwned
    let owned_clone: String = s.to_string();  // 用 Display/ToString

    // &[i32] 到 Vec<i32>
    let slice: &[i32] = &[1, 2, 3];
    let owned_vec: Vec<i32> = slice.to_owned();  // ToOwned
    let owned_clone2: Vec<i32> = slice.to_vec();  // 专用方法

    // str 是一个特殊的类型,它没有所有权
    // to_owned() 返回 String
    let s2: &str = "world";
    let owned2: String = ToOwned::to_owned(s2);

    println!("owned_str: {}", owned_str);
    println!("owned_clone: {}", owned_clone);
    println!("owned_vec: {:?}", owned_vec);
    println!("owned_clone2: {:?}", owned_clone2);
    println!("owned2: {}", owned2);

    // 打印结果:
    // owned_str: hello
    // owned_clone: hello
    // owned_vec: [1, 2, 3]
    // owned_clone2: [1, 2, 3]
    // owned2: world
}

ToOwned 的优势:

ToOwned 比 Clone 更通用。Clone 要求 &self -> Self,但 ToOwned 的返回类型可以是"拥有相同数据但类型不同"的东西。

例如,strToOwned<String>,因为 &str 可以变成 String(不同的类型)。但 String 不是 Clone 自 &str,因为 Clone 只能从 &StringString

5.2.15 Iterator Trait

Iterator 是 Rust 中最强大的 trait 之一,它定义了迭代器的基本行为。通过实现 Iterator,你可以让任何类型成为可迭代的。

5.2.15.1 Iterator trait 定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub trait Iterator {
    type Item;  // 迭代器的元素类型

    fn next(&mut self) -> Option<Self::Item>;

    // 还有很多默认方法...
    fn collect<B>(self) -> B where B: FromIterator<Self::Item> { ... }
    fn map<B, F>(self, f: F) -> Map<Self, F> { ... }
    fn filter<P>(self, predicate: P) -> Filter<Self, P> { ... }
    // ... 还有几十个
}

Iterator trait 是 Rust 迭代器的核心,它只有一个必须实现的方法:next

5.2.15.2 next 方法

next() 返回下一个元素,用 Option 包装:

  • Some(item) - 有下一个元素
  • None - 迭代结束
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct Counter {
    current: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Self {
        Counter { current: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.max {
            self.current += 1;
            Some(self.current)
        } else {
            None
        }
    }
}

fn main() {
    let mut counter = Counter::new(5);

    println!("next: {:?}", counter.next());  // Some(1)
    println!("next: {:?}", counter.next());  // Some(2)
    println!("next: {:?}", counter.next());  // Some(3)
    println!("next: {:?}", counter.next());  // Some(4)
    println!("next: {:?}", counter.next());  // Some(5)
    println!("next: {:?}", counter.next());  // None
    println!("next: {:?}", counter.next());  // None (之后永远是 None)

    // 打印结果:
    // next: Some(1)
    // next: Some(2)
    // next: Some(3)
    // next: Some(4)
    // next: Some(5)
    // next: None
    // next: None
}

5.2.15.3 IntoIterator trait

IntoIterator 让你可以用 for item in collection 的语法。

1
2
3
4
5
6
pub trait IntoIterator<Item = Self::Item> {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;

    fn into_iter(self) -> Self::IntoIter;
}

实现 IntoIterator 后,你就可以用 for 循环遍历你的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct Fibonacci {
    current: u64,
    next: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { current: 0, next: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.current;
        self.current = self.next;
        self.next = current + self.next;
        Some(current)
    }
}

impl IntoIterator for Fibonacci {
    type Item = u64;
    type IntoIter = Fibonacci;

    fn into_iter(self) -> Self::IntoIter {
        self
    }
}

fn main() {
    let fib = Fibonacci::new();

    // IntoIterator 让我们可以这样写
    for (i, n) in fib.take(10).enumerate() {
        println!("F{} = {}", i, n);
    }

    // 打印结果:
    // F0 = 0
    // F1 = 1
    // F2 = 1
    // F3 = 2
    // F4 = 3
    // F5 = 5
    // F6 = 8
    // F7 = 13
    // F8 = 21
    // F9 = 34
}

5.2.15.4 for 循环与 IntoIterator 的交互

for 循环实际上是语法糖,它会被展开成 into_iter() 调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn main() {
    let v = vec![1, 2, 3];

    // 这是
    for item in v {
        println!("{}", item);
    }

    // 展开后等价于:
    let mut iter = v.into_iter();
    while let Some(item) = iter.next() {
        println!("{}", item);
    }

    // 打印结果:
    // 1
    // 2
    // 3
    // 1
    // 2
    // 3
}

三种迭代方式:

方式语法消耗所有权返回类型
iter()for item in &v&Item
iter_mut()for item in &mut v&mut Item
into_iter()for item in vItem

5.2.16 Fn / FnMut / FnOnce Trait

这三个 trait 描述了闭包(closure)和函数(function)的行为。它们是 Rust 函数式编程的基础。

5.2.16.1 Fn trait

Fn 表示"可以多次调用,且不修改捕获的变量"。

1
2
3
pub trait Fn<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

Fn 闭包可以:

  • 被多次调用
  • 读取捕获的变量
  • 不能修改捕获的变量
  • 可以被复制(如果所有捕获的东西都是 Copy)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let x = 10;

    // Fn 闭包:只读取 x
    let print_x = || println!("x = {}", x);

    print_x();
    print_x();
    print_x();  // 想调用多少次都行

    // 打印结果:
    // x = 10
    // x = 10
    // x = 10
}

5.2.16.2 FnMut trait

FnMut 表示"可以多次调用,且可以修改捕获的变量"。

1
2
3
pub trait FnMut<Args>: Fn<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

FnMut 闭包可以:

  • 被多次调用
  • 读取和修改捕获的变量
  • 因为会修改环境,所以不能被复制
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
    let mut count = 0;

    // FnMut 闭包:修改捕获的 count
    let mut increment = || {
        count += 1;
        println!("count = {}", count);
    };

    increment();
    increment();
    increment();

    println!("最终 count = {}", count);

    // 打印结果:
    // count = 1
    // count = 2
    // count = 3
    // 最终 count = 3
}

5.2.16.3 FnOnce trait

FnOnce 表示"只能被调用一次"。这听起来像是个限制,但实际上很多场景下你只需要调用一次。

1
2
3
pub trait FnOnce<Args> {
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

FnOnce 闭包可以:

  • 只被调用一次
  • 可以消耗(move)捕获的变量
  • 因为会消耗环境,所以不能被复制或再次调用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let data = vec![1, 2, 3];

    // FnOnce 闭包:消耗 data
    let consume_data = || {
        println!("消耗掉: {:?}", data);
        // data 在这里被消耗了,不能再用了
    };

    consume_data();
    // consume_data();  // 编译错误!已经调用过了

    // 打印结果:
    // 消耗掉: [1, 2, 3]
}

5.2.16.4 三者的继承关系

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
FnOnce
FnMut
Fn

继承关系解释:
- Fn: 什么都不消耗,可以多次调用
- FnMut: 可能消耗/修改,可以多次调用
- FnOnce: 一定消耗,只能调用一次

所有 Fn 都是 FnMut,所有 FnMut 都是 FnOnce。
但反过来不成立。

类比理解:
- Fn = 图书馆的书:可以反复读
- FnMut = 笔记本:可以反复写
- FnOnce = 一次性口罩:用完就扔
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
fn apply<F>(f: F)
where
    F: Fn(),  // Fn 约束
{
    f();
    f();
}

fn apply_once<F>(f: F)
where
    F: FnOnce(),  // FnOnce 约束
{
    f();
    // f();  // 错误!
}

fn main() {
    let x = 5;
    let print_fn = || println!("Fn: {}", x);
    apply(print_fn);  // Fn 可以传给 Fn/FnMut/FnOnce 约束

    let mut y = 5;
    let mut increment = || { y += 1; println!("FnMut: {}", y); };
    // 错误!apply 需要 Fn 约束,但 increment 是 FnMut,不是 Fn
    // apply(increment);  // 取消注释会编译失败
    // 注意:FnMut 可以传给 FnMut/FnOnce 约束,但不能传给 Fn 约束
    // 正确做法:把 apply 改成 FnMut 约束

    let data = vec![1, 2];
    let consume = || println!("FnOnce: {:?}", data);
    apply_once(consume);  // FnOnce 只能传给 FnOnce
}

5.2.16.5 闭包的 trait 实现

在 Rust 中,闭包会自动实现这三个 trait 中的一个(或者多个)。具体实现哪个,取决于闭包如何使用捕获的变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 闭包类型 | 能实现哪些 Fn trait
// --------|----------------------
// 只读    | Fn, FnMut, FnOnce
// 写      | FnMut, FnOnce
// 消耗    | FnOnce

fn main() {
    // 场景1:只读捕获 -> Fn
    let a = 10;
    let fn_closure = || a * 2;  // Fn
    println!("fn_closure(): {}", fn_closure());

    // 场景2:修改捕获 -> FnMut
    let mut b = 10;
    let fnmut_closure = || { b *= 2; b };  // FnMut
    println!("fnmut_closure(): {}", fnmut_closure());

    // 场景3:消耗捕获 -> FnOnce
    let c = vec![1, 2];
    let fnonce_closure = || {
        let _owned = c;  // 消耗 c
        42
    };
    println!("fnonce_closure(): {}", fnonce_closure());

    // 打印结果:
    // fn_closure(): 20
    // fnmut_closure(): 20
    // fnonce_closure(): 42
}

5.3 Trait 对象与动态分发

想象一下,你经营一家动物园。动物园里有老虎、兔子、鸭子……每天你要给它们喂食。但问题是:老虎吃肉,兔子吃草,鸭子吃虫子。你不可能写一个函数同时处理所有情况,因为你需要在运行时才知道具体是哪种动物

在编程世界里,这种"需要在运行时决定具体类型"的场景叫做多态(Polymorphism)。Rust 用 trait 对象(Trait Object) 来解决这个问题。

5.3.1 dyn Trait 语法

dyn Trait 是 Rust 中表示"trait 对象"的语法。它代表"实现了某个 trait 的任意类型"。

5.3.1.1 trait 对象定义

1
2
3
4
// 使用 dyn Trait 作为类型
let animal: &dyn Animal;
// 或者 Box<dyn Animal>
let animal: Box<dyn Animal>;

dyn 关键字告诉 Rust:“这是一个动态大小的类型,它的实际类型要在运行时才能确定”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
trait Animal {
    fn make_sound(&self) -> String;
}

struct Dog;
struct Cat;
struct Duck;

impl Animal for Dog {
    fn make_sound(&self) -> String {
        "汪汪汪!".to_string()
    }
}

impl Animal for Cat {
    fn make_sound(&self) -> String {
        "喵喵喵!".to_string()
    }
}

impl Animal for Duck {
    fn make_sound(&self) -> String {
        "嘎嘎嘎!".to_string()
    }
}

fn main() {
    // animal 是 trait 对象,它指向一个 Dog
    let animal: &dyn Animal = &Dog;

    // 调用方法时,Rust 会在运行时决定调用哪个具体实现
    println!("狗叫: {}", animal.make_sound());
    // 打印结果: 狗叫: 汪汪汪!
}

5.3.1.2 虚表(vtable)机制

trait 对象背后是虚表(vtable,Virtual Table) 的魔法。当 Rust 创建 trait 对象时,它会:

  1. 创建一个指向数据的指针
  2. 创建一个指向 vtable 的指针

vtable 包含了每个方法的具体实现地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
trait 对象内存布局:

┌─────────────────────────────────────┐
│  Data Pointer  ──────────────────┐  │
│  (指向实际数据)                    │  │
├─────────────────────────────────────┤  │
│  VTable Pointer ──────────────┐   │  │
│  (指向虚表)                      │   │  │
└─────────────────────────────────────┘  │
                                      │  │
       ┌──────────────────────────────┘
┌─────────────────────────────────────┐
│  vtable for Dog:                    │
│  - make_sound: 0x1000 (地址)        │
│  - ...                              │
├─────────────────────────────────────┤
│  vtable for Cat:                   │
│  - make_sound: 0x2000 (地址)        │
│  - ...                              │
└─────────────────────────────────────┘

这就是为什么 trait 对象有运行时开销:每次方法调用都要通过 vtable 查找,而不是像静态分发那样直接内联。

5.3.1.3 胖指针(Fat Pointer)

我们把同时包含数据指针和 vtable 指针的指针叫做胖指针(Fat Pointer)。普通的 &T 是单个指针(8 字节 on 64-bit 系统),但 &dyn Trait 是两个指针(16 字节)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use std::mem;

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

impl Shape for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn main() {
    let circle = Circle { radius: 2.0 };

    // 胖指针的大小
    println!("&Circle 大小: {} 字节", mem::size_of::<&Circle>());
    println!("&dyn Shape 大小: {} 字节", mem::size_of::<&dyn Shape>());
    println!("Box<dyn Shape> 大小: {} 字节", mem::size_of::<Box<dyn Shape>>());

    // 打印结果:
    // &Circle 大小: 8 字节
    // &dyn Shape 大小: 16 字节
    // Box<dyn Shape> 大小: 16 字节
    // (在 64 位系统上)
}

5.3.2 Trait 对象的使用场景

Trait 对象适合那些"需要异构集合"或"需要运行时多态"的场景。

5.3.2.1 异构集合

异构集合是指集合中包含不同类型的元素。在使用泛型时,你只能放同类型的元素;但使用 trait 对象,你可以放各种不同的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
trait Printable {
    fn print(&self);
}

struct Document { content: String }
struct Image { data: Vec<u8>, format: String }
struct Video { duration_secs: u32 }

impl Printable for Document {
    fn print(&self) {
        println!("📄 文档: {}", self.content);
    }
}

impl Printable for Image {
    fn print(&self) {
        println!("🖼️ 图片: {} 格式, {} 字节", self.format, self.data.len());
    }
}

impl Printable for Video {
    fn print(&self) {
        println!("🎬 视频: {} 秒", self.duration_secs);
    }
}

fn main() {
    // 异构集合!包含 Document, Image, Video 三种类型
    let items: Vec<Box<dyn Printable>> = vec![
        Box::new(Document { content: "Hello World".to_string() }),
        Box::new(Image { data: vec![0u8; 1024], format: "PNG".to_string() }),
        Box::new(Video { duration_secs: 120 }),
    ];

    // 统一遍历调用
    for item in &items {
        item.print();
    }

    // 打印结果:
    // 📄 文档: Hello World
    // 🖼️ 图片: PNG 格式, 1024 字节
    // 🎬 视频: 120 秒
}

5.3.2.2 运行时多态

当你在编译时不知道具体类型,只能在运行时决定时,trait 对象就是你的好帮手。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
use std::io::{self, Write};

trait Operation {
    fn execute(&self, a: i32, b: i32) -> i32;
}

struct Add;
struct Subtract;
struct Multiply;

impl Operation for Add {
    fn execute(&self, a: i32, b: i32) -> i32 {
        a + b
    }
}

impl Operation for Subtract {
    fn execute(&self, a: i32, b: i32) -> i32 {
        a - b
    }
}

impl Operation for Multiply {
    fn execute(&self, a: i32, b: i32) -> i32 {
        a * b
    }
}

// 运行时根据用户输入决定用哪个操作
fn get_operation(op: &str) -> Box<dyn Operation> {
    match op {
        "+" => Box::new(Add),
        "-" => Box::new(Subtract),
        "*" => Box::new(Multiply),
        _ => panic!("未知操作符: {}", op),
    }
}

fn main() {
    let mut input = String::new();

    print!("输入操作符 (+, -, *): ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut input).unwrap();
    let op = input.trim();

    let operation = get_operation(op);

    let result = operation.execute(10, 5);
    println!("10 {} 5 = {}", op, result);

    // 示例交互:
    // 输入 + -> 10 + 5 = 15
    // 输入 - -> 10 - 5 = 5
    // 输入 * -> 10 * 5 = 50
}

5.3.2.3 回调与策略模式

Trait 对象常用于实现回调系统和策略模式。你可以把函数/策略封装成 trait 对象,然后传给其他函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
trait Strategy {
    fn execute(&self, data: &[i32]) -> i32;
}

struct SumStrategy;
struct MaxStrategy;
struct MinStrategy;

impl Strategy for SumStrategy {
    fn execute(&self, data: &[i32]) -> i32 {
        data.iter().sum()
    }
}

impl Strategy for MaxStrategy {
    fn execute(&self, data: &[i32]) -> i32 {
        *data.iter().max().unwrap_or(&0)
    }
}

impl Strategy for MinStrategy {
    fn execute(&self, data: &[i32]) -> i32 {
        *data.iter().min().unwrap_or(&0)
    }
}

// 接受策略作为参数
fn process_data(data: &[i32], strategy: &dyn Strategy) -> i32 {
    println!("使用策略处理数据: {:?}", data);
    let result = strategy.execute(data);
    println!("结果: {}", result);
    result
}

fn main() {
    let data = vec![1, 5, 3, 9, 2, 8];

    println!("=== 数据处理演示 ===");
    process_data(&data, &SumStrategy);
    println!();
    process_data(&data, &MaxStrategy);
    println!();
    process_data(&data, &MinStrategy);

    // 打印结果:
    // === 数据处理演示 ===
    // 使用策略处理数据: [1, 5, 3, 9, 2, 8]
    // 结果: 28
    //
    // 使用策略处理数据: [1, 5, 3, 9, 2, 8]
    // 结果: 9
    //
    // 使用策略处理数据: [1, 5, 3, 9, 2, 8]
    // 结果: 1
}

5.3.3 静态分发 vs 动态分发

Rust 同时支持两种分发方式,各有优劣。

5.3.3.1 泛型 <T: Trait>

泛型约束使用静态分发:编译器为每种具体类型生成专门的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
trait Animal {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Animal for Dog {
    fn speak(&self) -> String {
        "汪!".to_string()
    }
}

impl Animal for Cat {
    fn speak(&self) -> String {
        "喵~".to_string()
    }
}

// 静态分发:泛型函数
fn animal_speak<T: Animal>(animal: &T) {
    println!("{}", animal.speak());
}

fn main() {
    let dog = Dog;
    let cat = Cat;

    animal_speak(&dog);
    animal_speak(&cat);

    // 编译后实际上有两份 animal_speak 函数:
    // - animal_speak::<Dog>
    // - animal_speak::<Cat>

    // 打印结果:
    // 汪!
    // 喵~
}

5.3.3.2 dyn Trait

dyn Trait 使用动态分发:运行时通过 vtable 查找方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 动态分发:trait 对象
fn animal_speak_dynamic(animal: &dyn Animal) {
    println!("{}", animal.speak());
}

fn main() {
    let dog = Dog;
    let cat = Cat;

    animal_speak_dynamic(&dog);
    animal_speak_dynamic(&cat);

    // 编译后只有一份函数,通过 vtable 调用

    // 打印结果:
    // 汪!
    // 喵~
}

5.3.3.3 何时选择

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
选择静态分发(泛型 <T: Trait>)的场景:
✓ 编译时知道具体类型
✓ 性能关键,零运行时开销
✓ 需要内联优化
✓ 同类型集合(Vec<T> 而非 Vec<&dyn T>)

选择动态分发(dyn Trait)的场景:
✓ 需要异构集合
✓ 运行时才知道类型
✓ 需要减少编译后代码大小(泛型会生成多份代码)
✓ 插件系统、回调、策略模式
✓ 暴露 C API(dyn Trait 更适合跨语言边界)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
use std::time::Instant;

trait Compute {
    fn run(&self) -> u64;
}

struct HeavyComputation;
struct LightComputation;

impl Compute for HeavyComputation {
    fn run(&self) -> u64 {
        // 模拟耗时计算
        (0..1_000_000).map(|x| x * x).sum::<u64>() as u64
    }
}

impl Compute for LightComputation {
    fn run(&self) -> u64 {
        42
    }
}

// 静态分发版本
fn static_compute<C: Compute>(c: &C) -> u64 {
    c.run()
}

// 动态分发版本
fn dynamic_compute(c: &dyn Compute) -> u64 {
    c.run()
}

fn main() {
    let heavy = HeavyComputation;

    // 性能对比
    let start = Instant::now();
    for _ in 0..1000 {
        static_compute(&heavy);
    }
    println!("静态分发耗时: {:?}", start.elapsed());

    let start = Instant::now();
    for _ in 0..1000 {
        dynamic_compute(&heavy);
    }
    println!("动态分发耗时: {:?}", start.elapsed());

    // 动态分发通常稍慢一点(vtable 查找开销)
    // 但在实际应用中,这个差距通常可以忽略
}

5.3.4 Box 与 &dyn Trait

trait 对象可以有不同的"包装"形式,每种形式有不同的所有权语义。

5.3.4.1 Box

Box<dyn Trait> 把 trait 对象放在堆上,整个值拥有所有权。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
trait Drawable {
    fn draw(&self);
}

struct Circle { x: f64, y: f64, radius: f64 }
struct Square { x: f64, y: f64, side: f64 }

impl Drawable for Circle {
    fn draw(&self) {
        println!("绘制圆形: 圆心({},{}), 半径{}", self.x, self.y, self.radius);
    }
}

impl Drawable for Square {
    fn draw(&self) {
        println!("绘制正方形: 左上角({},{}), 边长{}", self.x, self.y, self.side);
    }
}

fn render(shape: Box<dyn Drawable>) {
    // Box<dyn Drawable> 拥有所有权
    shape.draw();
}

fn main() {
    // 创建堆分配的 trait 对象
    let circle = Box::new(Circle { x: 0.0, y: 0.0, radius: 1.0 });
    let square = Box::new(Square { x: 1.0, y: 1.0, side: 2.0 });

    render(circle);   // 所有权转移给 render,circle 在这里被消耗
    // render(square);  // 如果取消注释,square 也被移动,但 circle 已经在上面被消耗了

    // 注意:render 消耗(move)它的参数,所以每个 Box<dyn Drawable>只能用一次

    // 打印结果:
    // 绘制圆形: 圆心(0,0), 半径1
}

5.3.4.2 &dyn Trait

&dyn Trait 是借用 trait 对象,不获取所有权。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn describe(shape: &dyn Drawable) {
    // 只借用,不需要所有权
    shape.draw();
}

fn main() {
    let circle = Circle { x: 0.0, y: 0.0, radius: 1.0 };
    let square = Square { x: 1.0, y: 1.0, side: 2.0 };

    // 借用
    describe(&circle);
    describe(&square);

    // 借用后,原对象还能用
    println!("圆形还在: {:?}", circle.radius);

    // 打印结果:
    // 绘制圆形: 圆心(0,0), 半径1
    // 绘制正方形: 左上角(1,1), 边长2
    // 圆形还在: 1.0
}

5.3.4.3 Arc

Arc<dyn Trait> 用于多所有权场景:多个线程/模块需要共享同一个 trait 对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
use std::sync::Arc;
use std::thread;

trait Task {
    fn execute(&self);
}

struct BackgroundTask { name: String }
struct LightTask { name: String }

impl Task for BackgroundTask {
    fn execute(&self) {
        println!("执行后台任务: {}", self.name);
    }
}

impl Task for LightTask {
    fn execute(&self) {
        println!("执行轻量任务: {}", self.name);
    }
}

fn main() {
    // 创建 Arc 包装的 trait 对象
    let task1 = Arc::new(BackgroundTask { name: "数据备份".to_string() });
    let task2 = Arc::new(LightTask { name: "日志清理".to_string() });

    // 在多个线程中使用(简化示例,单线程模拟多线程)
    let t1 = task1.clone();
    let t2 = task2.clone();

    // 模拟线程1
    println!("线程1开始");
    t1.execute();

    // 模拟线程2
    println!("线程2开始");
    t2.execute();

    // task1 和 task2 还能用(Arc 的所有权)
    println!("任务完成");

    // 打印结果:
    // 线程1开始
    // 执行后台任务: 数据备份
    // 线程2开始
    // 执行轻量任务: 日志清理
    // 任务完成
}

5.3.5 对象安全(Object Safety)

不是所有的 trait 都可以作为 trait 对象。只有"对象安全(Object Safe)“的 trait 才能被用作 dyn Trait。这是因为 trait 对象缺少具体类型信息,某些方法无法在运行时调用。

5.3.5.1 对象安全规则

一个 trait 是对象安全的条件:

  1. 所有方法都是对象安全的
  2. trait 本身不能有泛型方法
  3. 不能返回 Self

方法对象安全的要求:

  • 不能有泛型参数 <T>
  • 如果返回 Self,不能在 &self 方法中返回(但在 &mut selfstatic 方法中可以)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 这个 trait 是对象安全的
trait Safe {
    fn method(&self);
    fn another(&self) -> i32;
}

// 这个 trait 不是对象安全的
trait NotSafe: Clone {
    fn generic_method<T>(&self, value: T);  // 泛型方法
    // fn returns_self(&self) -> Self;  // 返回 Self
}

5.3.5.2 返回 Self 的方法

返回 Self 的方法在 trait 对象中是有问题的,因为 trait 对象不知道 Self 的具体类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 这个 trait 不能作为 dyn Trait
trait Clonable {
    fn clone(&self) -> Self;  // 返回 Self
}

// 原因:Box<dyn Clonable>.clone() 返回什么类型?Box<dyn Clonable>?

// 但标准库的 Clone trait 为什么可以?答案在于:
// Clone trait 有隐式的 Sized 约束:Clone: Sized
// 这意味着 Clone trait 不能用于 dyn Trait
// Rust 特殊处理了 Clone,让它可以显式地调用 .clone(),但不能作为 trait 对象
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 正确的设计:使用泛型或关联类型
trait Factory {
    type Product;

    fn create(&self) -> Self::Product;
}

trait Serializer {
    fn serialize(&self) -> Vec<u8>;
}

// 这两个都是对象安全的
struct MyData { value: i32 }

impl Factory for MyData {
    type Product = Self;
    fn create(&self) -> Self::Product {
        MyData { value: self.value }
    }
}

impl Serializer for MyData {
    fn serialize(&self) -> Vec<u8> {
        format!("{}", self.value).into_bytes()
    }
}

fn main() {
    // dyn Factory 是可以的(虽然不常用)
    let data: &dyn Factory<Product = MyData> = &MyData { value: 42 };
    let new_data = data.create();
    println!("创建: {:?}", new_data.value);

    // dyn Serializer 更常见
    let ser: &dyn Serializer = &MyData { value: 42 };
    let bytes = ser.serialize();
    println!("序列化: {:?}", bytes);

    // 打印结果:
    // 创建: 42
    // 序列化: [52, 50]
}

5.3.5.3 泛型方法

泛型方法让 trait 无法成为 trait 对象,因为泛型在编译时需要单态化,但 trait 对象运行时才知道具体类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 这个 trait 不是对象安全的
trait Generic {
    fn process<T>(&self, value: T);
}

// 尝试创建 dyn Generic 会失败
// let _: Box<dyn Generic>;  // 编译错误!

// 但如果你真的需要泛型行为,可以这样:
trait Flexible {
    fn process_boxed(&self, value: Box<dyn std::any::Any>);
}

struct Handler;

impl Flexible for Handler {
    fn process_boxed(&self, value: Box<dyn std::any::Any>) {
        // 可以尝试 downcast
        if let Ok(n) = value.downcast_ref::<i32>() {
            println!("收到 i32: {}", n);
        } else if let Ok(s) = value.downcast_ref::<String>() {
            println!("收到 String: {}", s);
        }
    }
}

fn main() {
    let handler = Handler;
    handler.process_boxed(Box::new(42_i32));
    handler.process_boxed(Box::new("hello".to_string()));

    // 打印结果:
    // 收到 i32: 42
    // 收到 String: hello
}

本章小结

本章我们深入学习了 Rust 的 trait 系统:

  • Trait 基础:trait 是 Rust 版的"接口”,定义行为规范,由具体类型来实现
  • Trait 约束与静态分发:泛型约束 <T: Trait>where T: Trait 实现编译时静态分发
  • 默认方法实现:trait 可以提供默认方法实现,实现者可以选择覆盖或使用默认
  • Trait 继承:子 trait 继承父 trait,实现子 trait 必须同时实现所有父 trait
  • 孤儿规则:为类型实现 trait 需要类型或 trait 至少有一个是本地定义的
  • 标准库常用 Trait:Debug、Display、Clone、Copy、Drop、Default、From/Into、AsRef/AsMut、Borrow/BorrowMut、Eq/PartialEq、Ord/PartialOrd、Hash、ToOwned、Iterator、IntoIterator、Fn/FnMut/FnOnce
  • Trait 对象与动态分发dyn Trait 实现运行时多态,通过 vtable 查找方法
  • 对象安全:只有对象安全的 trait 才能作为 dyn Trait

Trait 是 Rust 类型系统的灵魂,掌握它你就能写出优雅、灵活、类型安全的代码!