Rustlings enums 章节精讲:用枚举变体与模式匹配表达代数数据类型
【免费下载链接】rustlings:crab: Small exercises to get you used to reading and writing Rust code!项目地址: https://gitcode.com/gh_mirrors/ru/rustlings
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 等章节中多线程消息传递的常见写法。
【免费下载链接】rustlings:crab: Small exercises to get you used to reading and writing Rust code!项目地址: https://gitcode.com/gh_mirrors/ru/rustlings
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考