Rustlings enums 章节精讲:用枚举变体与模式匹配表达代数数据类型
Rust 的枚举(enum)是一种"枚举出所有可能取值"的类型定义机制,Rustlings 的 enums 章节将其定位为与函数式语言(F#、OCaml、Haskell)中代数数据类型(ADT)相当的核心特性,并强调其与模式匹配(pattern matching)配合使用的价值。本篇围绕 exercises/08_enums 下的三道递进式练习(enums1、enums2、enums3)展开:从定义最简单的单元变体,到为变体携带数据(结构体式、元组式两种形式),再到用 match 对枚举进行解构分发,最后结合 Rustlings 工具链的 run / hint 机制说明如何在本仓库中实际完成并验证这一章节。
为什么 Rust 的枚举与其他语言不同
章节 README 开篇即指出:枚举是许多语言都具备的特性,但各语言的枚举能力参差不齐,Rust 的枚举最接近函数式语言的代数数据类型。其关键差异在于:Rust 的每个变体(variant)可以携带不同的数据,并且编译器强制对枚举做穷尽处理。
对照 exercises/README.md 中的"练习到书章节映射表",enums 对应 The Rust Book 的 §6(枚举与模式匹配)与 §19.3(模式语法),这也解释了本章练习的组织方式——先认识变体形态,再落到模式匹配上。
Rustlings 仓库为每个练习同时维护了两份文件:待完成的练习文件(exercises/ 下,含 TODO 占位)与参考解答(solutions/ 下)。例如 enums1 的练习与解答分别是 exercises/08_enums/enums1.rs 和 solutions/08_enums/enums1.rs,这种"题目 + 标准答案"的结构本身就是理解每个知识点的最佳路径。
enums1:单元变体与 #[derive(Debug)]
练习文件 enums1.rs 给出了骨架:
#[derive(Debug)]
enum Message {
// TODO: Define a few types of messages as used below.
}
fn main() {
println!("{:?}", Message::Resize);
println!("{:?}", Message::Move);
println!("{:?}", Message::Echo);
println!("{:?}", Message::ChangeColor);
println!("{:?}", Message::Quit);
}
main 函数用 {:?}(调试格式)打印了五个变体,因此需要补全的正是这五个不携带数据的单元变体。标准解答 为:
#[derive(Debug)]
enum Message {
Resize,
Move,
Echo,
ChangeColor,
Quit,
}
这里有两个值得注意的实现细节:
#[derive(Debug)]的必要性:{:?}格式化要求类型实现Debugtrait。单元变体枚举无法手动感知内容,derive宏会在编译期为Message自动生成Debug实现,直接打印变体名(如Resize)。若移除derive,println!("{:?}", Message::Resize)会因Message未实现Debug而编译失败。- 单元变体(unit-like variant)的语义:这种形态等价于 C 的枚举常量,仅表示"某件事发生了",不携带任何状态。这也是为什么 info.toml 中该练习的 hint 只有一句 "No hints this time ;)"(见 rustlings-macros/info.toml 第 434 行附近)——它被设计成本章的热身。
用 Rustlings 运行与查看提示的命令(命令定义见 src/cli.rs 中的 Command 枚举):
rustlings run enums1 # 编译并运行该练习,通过后标记为 done
rustlings hint enums1 # 输出该练习的提示
enums2:让变体携带数据——三种变体形态
第二个练习 enums2.rs 要求变体携带真实数据。练习给出的构造表达式已经暗示了每个变体应有的形态:
#[derive(Debug)]
struct Point {
x: u64,
y: u64,
}
#[derive(Debug)]
enum Message {
// TODO: Define the different variants used below.
}
impl Message {
fn call(&self) {
println!("{self:?}");
}
}
fn main() {
let messages = [
Message::Resize { width: 10, height: 30 },
Message::Move(Point { x: 10, y: 15 }),
Message::Echo(String::from("hello world")),
Message::ChangeColor(200, 255, 255),
Message::Quit,
];
for message in &messages {
message.call();
}
}
info.toml 中为 enums2 配置的官方 hint 明确给出了三种变体形态的命名与示例:
Enum variants can be defined using three different forms: struct-, tuple- and unit-like.
enum EnumUsingAllVariantForms { StructLike { named_field: bool }, TupleLike(bool), UnitLike, }
对照 main 中的构造方式,可以逐个确定每个变体的形态与字段类型:
| 变体 | 构造方式 | 形态 | 应定义的类型 |
|---|---|---|---|
Resize |
Resize { width: 10, height: 30 } |
结构体式(struct-like) | Resize { width: u64, height: u64 } |
Move |
Move(Point { x: 10, y: 15 }) |
元组式(tuple-like),携带一个 struct Point 值 |
Move(Point) |
Echo |
Echo(String::from("hello world")) |
元组式,携带 String |
Echo(String) |
ChangeColor |
ChangeColor(200, 255, 255) |
元组式,携带三个无符号字节 | ChangeColor(u8, u8, u8) |
Quit |
Quit |
单元式(unit-like) | Quit |
solutions/08_enums/enums2.rs 中的完整定义为:
#[derive(Debug)]
enum Message {
Resize { width: u64, height: u64 },
Move(Point),
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
}
几个类型的取值细节值得留意:
width/height取u64,与Point的x、y字段类型一致(见练习文件中已给出的struct Point定义);ChangeColor(u8, u8, u8)三个分量对应 RGB 三原色,u8的取值范围 0~255 正好覆盖标准颜色通道的量程——这正是元组式变体适合表达"一组同质数据"的场景;- 元组式变体的分量是匿名的,只能按位置取值;结构体式变体是具名字段,可按名字取值。二者的取舍通常取决于字段是否有自解释的语义(宽高用名字更清晰,RGB 三通道用位置足矣)。
此外,练习还展示了枚举上的方法实现:impl Message { fn call(&self) } 接收 &self,以引用方式访问枚举值并打印。main 中通过 for message in &messages 遍历切片借用,逐个调用 message.call()——这说明枚举值与结构体一样,是普通类型,可以放进数组、取引用、实现方法,只是"类型的取值空间"被显式枚举了出来。
运行验证时注意该练习在 info.toml 中标记为 test = false,即 rustlings run enums2 只要求编译并正常执行、输出五个变体的 Debug 表示,没有断言测试。
enums3:用 match 解构枚举,驱动状态机
第三个练习 enums3.rs 把枚举、方法与单元测试串起来:Message 的定义已给出(同 enums2 解答),核心任务是实现 State::process,把每种消息分发给对应的处理方法:
impl State {
fn resize(&mut self, width: u64, height: u64) { ... }
fn move_position(&mut self, point: Point) { ... }
fn echo(&mut self, s: String) { ... }
fn change_color(&mut self, red: u8, green: u8, blue: u8) { ... }
fn quit(&mut self) { ... }
fn process(&mut self, message: Message) {
// TODO: Create a match expression to process the different message
// variants using the methods defined above.
}
}
官方 hint(info.toml)分两步给出指引:先定义枚举使其通过编译,再在 process() 中创建 match 表达式,并特别提醒"需要在 match 表达式中解构(deconstruct)某些消息变体以取出其值"。
solutions/08_enums/enums3.rs 中 process 的实现即本章"模式匹配"主题的落点:
fn process(&mut self, message: Message) {
match message {
Message::Resize { width, height } => self.resize(width, height),
Message::Move(point) => self.move_position(point),
Message::Echo(string) => self.echo(string),
Message::ChangeColor(red, green, blue) => self.change_color(red, green, blue),
Message::Quit => self.quit(),
}
}
这段代码体现了模式匹配解构变体的三种写法:
- 结构体式变体:
Message::Resize { width, height }按字段名解构,width、height直接成为匹配臂里的绑定变量; - 元组式变体:
Message::ChangeColor(red, green, blue)按位置解构三个u8分量;Message::Move(point)则是整体绑定Point值; - 单元式变体:
Message::Quit无需绑定,直接映射到self.quit()。
更关键的是 Rust 的穷尽性检查:match 必须覆盖 Message 的全部五个变体,遗漏任何一个(例如忘了 Quit)都会产生编译错误而非静默忽略。从 solutions/08_enums/enums3.rs 与 exercises/08_enums/enums3.rs 的对照可见,两者除 process 的实现和一处注释外完全一致——这正是 rustlings 的练习机制:练习文件删除关键代码段,解答文件补全后作为验收基准。
练习文件末尾的 #[cfg(test)] 测试模块则是"穷尽性"的直接验证器:
#[test]
fn test_match_message_call() {
let mut state = State {
width: 0,
height: 0,
position: Point { x: 0, y: 0 },
message: String::from("hello world"),
color: (0, 0, 0),
quit: false,
};
state.process(Message::Resize { width: 10, height: 30 });
state.process(Message::Move(Point { x: 10, y: 15 }));
state.process(Message::Echo(String::from("Hello world!")));
state.process(Message::ChangeColor(255, 0, 255));
state.process(Message::Quit);
assert_eq!(state.width, 10);
assert_eq!(state.height, 30);
assert_eq!(state.position.x, 10);
assert_eq!(state.position.y, 15);
assert_eq!(state.message, "Hello world!");
assert_eq!(state.color, (255, 0, 255));
assert!(state.quit);
}
测试依次向 State 投递五种消息,再逐一断言各字段是否按预期更新——width/height 变为 10/30、位置变为 (10, 15)、消息字符串被替换、颜色变为 (255, 0, 255)、quit 置为 true。由于该练习在 info.toml 中没有 test = false 标记,rustlings run enums3 会以 cargo test 方式执行此测试,全部断言通过才判定完成——这与 enums1/enums2 的"编译 + 运行"验收标准不同。
从源码结构看,这套"练习/解答双轨"机制由 dev/Cargo.toml 中的 target 清单驱动:每个练习注册为一对 target(如 enums1 与 enums1_sol,分别指向 ../exercises/08_enums/enums1.rs 与 ../solutions/08_enums/enums1.rs),配合 rustlings-macros/info.toml 中的 name、dir、test、hint 元数据,由 rustlings 的 run / hint / check-all 子命令(定义于 src/cli.rs)统一调度。想批量核验本章进度,可以执行 rustlings check-all 查看各练习的 done/pending 状态。
小结:本章练习的知识递进
| 练习 | 核心知识点 | 验收方式 |
|---|---|---|
| enums1 | 单元变体、#[derive(Debug)] 与 {:?} 打印 |
编译并运行(test = false) |
| enums2 | 结构体式 / 元组式 / 单元式三种变体形态、携带数据的变体、枚举上的 impl 方法 |
编译并运行(test = false) |
| enums3 | match 穷尽匹配与变体解构、以枚举消息驱动状态更新 |
编译 + cargo test 断言 |
这一章的完整学习闭环是:先理解"Rust 枚举 = 代数数据类型"这一定位,再依次掌握"变体能携带什么数据"(三种形态)与"如何把数据取出来"(模式匹配解构),最后依靠编译器的穷尽性检查与单元测试(test_match_message_call)双重保证分发逻辑没有遗漏。完成 exercises/08_enums 全部三个练习后,读者即可具备编写"消息驱动状态机"这类典型 Rust 模式的基础,这也是后续 20_threads 等章节中多线程消息传递的常见写法。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00