36 KiB
Rust 枚举(Enum)练习题
建议先手动写出每道题的答案,再运行代码验证。
目录
一、基础题:判断正误
判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。
题目 1-1
enum Direction {
North,
South,
East,
West,
}
fn main() {
let d = Direction::North;
println!("{:?}", d);
}
不能,缺少#[derive(Debug)]
题目 1-2
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
println!("home kind: {:?}, address: {}", home.kind, home.address);
}
不能,IpAddrKind缺少#[derive(Debug)]
题目 1-3
enum IpAddr {
V4(String),
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
可以通过
题目 1-4
fn main() {
let some_number = Some(5);
let some_string = Some("hello");
let absent_number: Option<i32> = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
}
题目 1-5
fn main() {
let x: Option<i32> = Some(5);
let y = x + 1;
println!("{}", y);
}
题目 1-6
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
}
}
fn main() {
println!("{}", value_in_cents(Coin::Quarter));
}
题目 1-7
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m = Message::Write(String::from("hello"));
let n = Message::Move { x: 10, y: 20 };
let q = Message::ChangeColor(255, 0, 0);
}
题目 1-8
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn main() {
let coin = Coin::Dime;
let mut count = 0;
if let Coin::Quarter = coin {
println!("State quarter!");
} else {
count += 1;
}
println!("count: {}", count);
}
二、填空题:补充代码
补全下列代码使其能通过编译并达到期望输出。
题目 2-1:定义简单枚举
// 填空:定义一个名为 TrafficLight 的枚举,包含 Red、Yellow、Green 三个变体
________ TrafficLight {
Red,
Yellow,
Green,
}
fn main() {
let light = TrafficLight::________;
match light {
TrafficLight::Red => println!("停止"),
TrafficLight::Yellow => println!("减速"),
TrafficLight::Green => println!("通行"),
}
// 期望输出:停止
}
题目 2-2:带数据的枚举
// 填空:定义一个名为 WebEvent 的枚举
// - PageLoad: 无数据
// - KeyPress: 包含一个 char
// - Click: 包含两个 i32(x 和 y 坐标)
enum WebEvent {
________,
KeyPress(char),
Click { x: i32, y: i32 },
}
fn main() {
let load = WebEvent::PageLoad;
let key = WebEvent::________('a');
let click = WebEvent::Click { x: 100, y: 200 };
println!("ok");
}
题目 2-3:Option 的使用
fn main() {
let v1 = Some(10);
let v2: Option<i32> = ________;
// 填空:使用 match 安全地提取 Option 中的值,None 时返回 0
let val1 = match v1 {
Some(n) => n,
None => 0,
};
let val2 = match v2 {
Some(n) => n,
________ => 0,
};
println!("val1: {}, val2: {}", val1, val2);
// 期望输出:val1: 10, val2: 0
}
题目 2-4:Result 的使用
fn divide(a: i32, b: i32) -> ________ {
if b == 0 {
Err(String::from("除数不能为零"))
} else {
________(a / b)
}
}
fn main() {
let result1 = divide(10, 2);
let result2 = divide(10, 0);
// 填空:使用 match 处理 Result
match result1 {
Ok(val) => println!("结果: {}", val),
Err(e) => println!("错误: {}", e),
}
match result2 {
Ok(val) => println!("结果: {}", val),
________ => println!("错误: {}", e),
}
// 期望输出:
// 结果: 5
// 错误: 除数不能为零
}
题目 2-5:枚举上的方法
enum Message {
Quit,
Write(String),
Move { x: i32, y: i32 },
}
impl Message {
// 填空:定义一个 call 方法,打印消息内容
fn ________(&self) {
match self {
Message::Quit => println!("退出"),
Message::Write(text) => println!("写入: {}", text),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
}
}
}
fn main() {
let m = Message::Write(String::from("你好世界"));
m.call(); // 期望输出:写入: 你好世界
let n = Message::Move { x: 10, y: 20 };
n.call(); // 期望输出:移动到 (10, 20)
}
题目 2-6:if let 语法
fn main() {
let config_max = Some(3u8);
// 填空:使用 if let 简化 match
________ = config_max {
println!("最大值为: {}", max);
}
// 等价于以下 match 写法:
// match config_max {
// Some(max) => println!("最大值为: {}", max),
// _ => (),
// }
// 期望输出:最大值为: 3
}
题目 2-7:嵌套 Option 处理
fn main() {
let opt: Option<Option<i32>> = Some(Some(42));
// 填空:使用模式匹配提取最内层的值
match opt {
Some(Some(inner)) => println!("值是: {}", ________),
Some(None) => println!("内层是 None"),
None => println!("外层是 None"),
}
// 期望输出:值是: 42
}
题目 2-8:match 的多种模式
fn main() {
let x = 3;
// 填空:使用 match 的多模式匹配和范围匹配
match x {
________ => println!("是 1 或 2"),
3..=8 => println!("在 3 到 8 之间"),
_ => println!("其他值"),
}
// 期望输出:在 3 到 8 之间
}
三、找出并修复错误
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
题目 3-1
enum Status {
Active,
Inactive,
}
fn main() {
let s = Status::Active;
println!("{}", s);
}
题目 3-2
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
println!("{}", sum);
}
题目 3-3
enum Number {
Integer(i32),
Float(f64),
}
fn main() {
let n = Number::Integer(10);
match n {
Number::Integer => println!("整数: {}", 0),
Number::Float => println!("浮点数: {}", 0.0),
}
}
题目 3-4
#[derive(Debug)]
enum Coin {
Penny,
Nickel,
Dime,
}
fn main() {
let coin = Coin::Penny;
let val = match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
};
println!("{}", val);
}
题目 3-5
enum Action {
Move(i32, i32),
Jump,
}
fn main() {
let action = Action::Move(10, 20);
if let Action::Move(x, y) = action {
println!("移动: x={}, y={}", x, y);
}
// 此处 action 的所有权是否还在?
match action {
Action::Jump => println!("跳跃"),
_ => (),
}
}
题目 3-6
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
fn area(shape: Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle(w, h) => w * h,
}
}
fn main() {
let s = Shape::Circle(5.0);
println!("面积: {}", area(s));
println!("面积: {}", area(s)); // 第二次调用
}
题目 3-7
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let target = 10;
let result = numbers.iter().find(|&&x| x == target);
println!("找到: {}", result);
}
题目 3-8
enum Color {
Rgb(u8, u8, u8),
Named(String),
}
fn main() {
let c = Color::Rgb(255, 0, 0);
let color_str = match c {
Color::Rgb(r, g, b) => format!("rgb({}, {}, {})", r, g, b),
Color::Named(name) => name,
};
println!("{}", color_str);
}
四、编程题
题目 4-1:文件系统节点
设计一个枚举表示文件系统中的节点(文件或目录),并实现相应方法。
// 定义枚举 FsNode
// - File: 包含文件名(String)和文件大小(u64)
// - Directory: 包含目录名(String)和子节点列表(Vec<FsNode>)
// 你的代码:定义 FsNode 枚举
impl FsNode {
// 1. 获取节点名称
fn name(&self) -> &str {
// 你的代码
}
// 2. 计算节点占用的总大小
// 文件直接返回大小;目录递归累加所有子节点大小
fn total_size(&self) -> u64 {
// 你的代码
}
// 3. 打印文件树结构(带缩进)
// print_tree(&self, depth: usize)
// 每层缩进 2 个空格
fn print_tree(&self, depth: usize) {
// 你的代码
}
// 4. 统计文件数量(不含目录)
fn file_count(&self) -> usize {
// 你的代码
}
}
fn main() {
let mut root = FsNode::Directory {
name: String::from("/"),
children: vec![
FsNode::File { name: String::from("readme.txt"), size: 1024 },
FsNode::Directory {
name: String::from("src"),
children: vec![
FsNode::File { name: String::from("main.rs"), size: 2048 },
FsNode::File { name: String::from("lib.rs"), size: 4096 },
],
},
FsNode::File { name: String::from("config.toml"), size: 512 },
],
};
root.print_tree(0);
println!("总大小: {} 字节", root.total_size());
println!("文件数量: {}", root.file_count());
// 期望输出:
// / (目录)
// readme.txt (1024 字节)
// src (目录)
// main.rs (2048 字节)
// lib.rs (4096 字节)
// config.toml (512 字节)
// 总大小: 7680 字节
// 文件数量: 3
}
题目 4-2:计算器
使用枚举实现一个简单的命令式计算器。
// 定义枚举 Operation
// - Add(i32, i32)
// - Subtract(i32, i32)
// - Multiply(i32, i32)
// - Divide(i32, i32)
// 你的代码:定义 Operation 枚举
impl Operation {
// 1. 执行运算并返回 Result<f64, String>
// 除法时检查除数是否为 0
fn execute(&self) -> Result<f64, String> {
// 你的代码
}
// 2. 返回该运算的符号字符串
fn symbol(&self) -> &str {
// 你的代码
}
// 3. 返回该运算的算式描述,如 "3 + 4"
fn describe(&self) -> String {
// 你的代码
}
}
fn main() {
let ops = vec![
Operation::Add(10, 5),
Operation::Subtract(10, 5),
Operation::Multiply(10, 5),
Operation::Divide(10, 2),
Operation::Divide(10, 0),
];
for op in &ops {
print!("{} = ", op.describe());
match op.execute() {
Ok(result) => println!("{}", result),
Err(e) => println!("错误: {}", e),
}
}
// 期望输出:
// 10 + 5 = 15
// 10 - 5 = 5
// 10 * 5 = 50
// 10 / 2 = 5
// 10 / 0 = 错误: 除数不能为零
}
题目 4-3:JSON 值类型
定义一个枚举表示简化的 JSON 值类型。
// 定义枚举 JsonValue
// - Null
// - Bool(bool)
// - Number(f64)
// - String(String)
// - Array(Vec<JsonValue>)
// - Object: 包含一个 Vec<(String, JsonValue)> 作为键值对
// 你的代码:定义 JsonValue 枚举
impl JsonValue {
// 1. 返回 JSON 类型的名称字符串,如 "Null", "Bool" 等
fn type_name(&self) -> &str {
// 你的代码
}
// 2. 将 JsonValue 转为格式化的字符串(递归实现)
// 格式要求:
// - Null -> "null"
// - Bool(b) -> "true"/"false"
// - Number(n) -> 格式化数字(保留有意义的小数)
// - String(s) -> "\"s\""
// - Array(arr) -> "[元素1, 元素2, ...]"
// - Object(pairs) -> "{\n key: value,\n ...\n}"
fn to_string(&self) -> String {
// 你的代码
}
}
fn main() {
let json = JsonValue::Object(vec![
(String::from("name"), JsonValue::String(String::from("张三"))),
(String::from("age"), JsonValue::Number(25.0)),
(String::from("is_student"), JsonValue::Bool(false)),
(String::from("scores"), JsonValue::Array(vec![
JsonValue::Number(85.5),
JsonValue::Number(92.0),
JsonValue::Number(78.5),
])),
(String::from("address"), JsonValue::Null),
]);
println!("类型: {}", json.type_name());
println!("{}", json.to_string());
// 期望输出(格式大致如下):
// 类型: Object
// {
// name: "张三",
// age: 25,
// is_student: false,
// scores: [85.5, 92, 78.5],
// address: null
// }
}
题目 4-4:状态机
使用枚举实现一个简单的订单状态机。
// 定义枚举 OrderState
// - Pending: 待支付
// - Paid { amount: f64 }: 已支付,记录支付金额
// - Shipped { tracking_id: String }: 已发货,记录快递单号
// - Delivered: 已签收
// - Cancelled { reason: String }: 已取消,记录取消原因
// 你的代码:定义 OrderState 枚举
impl OrderState {
// 1. 判断订单是否可以取消(只有 Pending 和 Paid 状态可以取消)
fn can_cancel(&self) -> bool {
// 你的代码
}
// 2. 支付订单(Pending -> Paid)
// 返回 Result<OrderState, String>
// 如果当前状态不是 Pending,返回错误
fn pay(self, amount: f64) -> Result<OrderState, String> {
// 你的代码
}
// 3. 发货(Paid -> Shipped)
// 返回 Result<OrderState, String>
// 如果当前状态不是 Paid,返回错误
fn ship(self, tracking_id: String) -> Result<OrderState, String> {
// 你的代码
}
// 4. 签收(Shipped -> Delivered)
// 返回 Result<OrderState, String>
fn deliver(self) -> Result<OrderState, String> {
// 你的代码
}
// 5. 取消订单,返回新的状态
fn cancel(self, reason: String) -> Result<OrderState, String> {
// 你的代码
}
// 6. 获取状态描述
fn describe(&self) -> String {
// 你的代码
}
}
fn main() {
let order = OrderState::Pending;
println!("初始状态: {}", order.describe());
let order = match order.pay(299.0) {
Ok(state) => state,
Err(e) => { println!("{}", e); return; }
};
println!("支付后: {}", order.describe());
let order = match order.ship(String::from("SF1234567890")) {
Ok(state) => state,
Err(e) => { println!("{}", e); return; }
};
println!("发货后: {}", order.describe());
let order = match order.deliver() {
Ok(state) => state,
Err(e) => { println!("{}", e); return; }
};
println!("签收后: {}", order.describe());
// 测试异常路径:已签收的订单不能再取消
match order.cancel(String::from("不需要了")) {
Ok(_) => println!("取消成功"),
Err(e) => println!("取消失败: {}", e),
}
// 期望输出:
// 初始状态: 待支付
// 支付后: 已支付 299 元
// 发货后: 已发货 (SF1234567890)
// 签收后: 已签收
// 取消失败: 当前状态 已签收 不允许取消
}
题目 4-5:表达式求值器
使用枚举定义一个简单的数学表达式树并实现求值。
// 定义枚举 Expr
// - Number(f64): 数字字面量
// - Add(Box<Expr>, Box<Expr>): 加法
// - Subtract(Box<Expr>, Box<Expr>): 减法
// - Multiply(Box<Expr>, Box<Expr>): 乘法
// - Divide(Box<Expr>, Box<Expr>): 除法
// - Negate(Box<Expr>): 取负
// 你的代码:定义 Expr 枚举
impl Expr {
// 1. 求值,返回 Result<f64, String>
fn eval(&self) -> Result<f64, String> {
// 你的代码
}
// 2. 返回表达式的中缀表示字符串,包含必要的括号
// 例如:(1 + 2) * 3
fn to_string(&self) -> String {
// 你的代码
}
}
fn main() {
// 构建表达式: (3 + 4) * (10 - 2) / -5
let expr = Expr::Divide(
Box::new(Expr::Multiply(
Box::new(Expr::Add(
Box::new(Expr::Number(3.0)),
Box::new(Expr::Number(4.0)),
)),
Box::new(Expr::Subtract(
Box::new(Expr::Number(10.0)),
Box::new(Expr::Number(2.0)),
)),
)),
Box::new(Expr::Negate(Box::new(Expr::Number(5.0)))),
);
println!("表达式: {}", expr.to_string());
match expr.eval() {
Ok(result) => println!("结果: {}", result),
Err(e) => println!("错误: {}", e),
}
// 期望输出:
// 表达式: ((3 + 4) * (10 - 2)) / (-5)
// 结果: -11.2
}
五、综合思考题
题目 5-1
分析以下代码中 match 的匹配过程。some_u8_value 的值为 3u8,在 match 中匹配到了哪条分支?Rust 的 | 模式和 ..= 范围模式分别适用于什么场景?
fn main() {
let some_u8_value = 3u8;
match some_u8_value {
1 => println!("one"),
2 | 3 | 4 => println!("two, three, or four"),
5..=10 => println!("five through ten"),
_ => println!("anything"),
}
}
题目 5-2
对比 match、if let 和 matches! 宏。在什么场景下分别应该使用哪种?以下三段代码分别适合用哪种方式改写?
// 场景 A:两个分支都需要处理
enum Coin { Penny, Nickel, Dime, Quarter }
fn value(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
// 场景 B:只关心一种模式,其余忽略
let opt = Some(42);
// 打印 opt 中的值(如果存在)
// 场景 C:只需要布尔判断
let x = Some(5);
// 判断 x 是否为 Some 且值大于 3
题目 5-3
Option<T> 和 Result<T, E> 都用于处理"可能存在或不存在"的情况,但语义不同。请说明:
Option<T>适用于什么场景?Result<T, E>适用于什么场景?- 如何将
Option<T>转换为Result<T, E>?(写出一段示例代码) - 如何将
Result<T, E>转换为Option<T>?(写出一段示例代码)
题目 5-4
以下代码中,msg 在第一次 match 后和第二次 match 后分别发生了什么?为什么 Rust 需要 match 的所有权机制?
#[derive(Debug)]
enum Message {
Write(String),
Quit,
}
fn main() {
let msg = Message::Write(String::from("hello"));
match msg {
Message::Write(s) => println!("写入: {}", s),
Message::Quit => println!("退出"),
}
// println!("{:?}", msg); // 取消注释会怎样?为什么?
}
题目 5-5
Rust 没有传统的 null,而是用 Option<T>。分析以下两种设计的优劣:
// 设计 A:C 风格,用特殊值表示"无"
struct User {
name: String,
middle_name: String, // 没有中间名时用空字符串 ""
age: Option<u32>, // 未知年龄时用 None
}
// 设计 B:全部使用 Option
struct User {
name: String,
middle_name: Option<String>,
age: Option<u32>,
}
middle_name在两种设计中分别如何表示"没有中间名"?- 哪种设计更符合 Rust 的惯用风格?为什么?
- 如果用户同时有
Option<String>和Option<String>类型的字段,它们各自为 None 的语义可能不同(如"未提供" vs "明确为空"),你会如何设计来区分这些情况?
参考答案
请独立完成再查看答案。
点击展开答案
一、基础题
1-1:❌ 编译错误。枚举变体不会自动实现任何 trait。需要 #[derive(Debug)] 或手动实现 Debug 才能使用 {:?} 打印。
1-2:❌ 编译错误。IpAddrKind 没有 derive Debug,所以 {:?} 无法打印 home.kind。需要加上 #[derive(Debug)]。
1-3:✅ 通过编译。枚举变体可以附带不同类型的数据,此例中 V4 和 V6 都附带 String。
1-4:✅ 通过编译。Option<T> 是 Rust 标准库预置的枚举,Some 和 None 可以直接使用,Option 实现了 Debug。None 需要类型标注因为它无法自动推断 T 的类型。
1-5:❌ 编译错误。Option<i32> 和 i32 是不同的类型,不能直接相加。需要先用 match 或 unwrap() 等方法提取值。
1-6:❌ 编译错误。match 必须穷尽所有可能(exhaustive)。Coin 有 4 个变体但只匹配了 3 个,缺少 Coin::Quarter 分支。需要补全或添加 _ => 通配分支。
1-7:✅ 通过编译。枚举变体可以使用不同的数据绑定方式:无数据(Quit)、命名结构体式(Move { x, y })、元组式(Write(String)、ChangeColor(i32, i32, i32))。
1-8:✅ 通过编译。if let 是 match 的语法糖,只匹配一种模式并忽略其余。这里匹配失败走 else 分支,count 变为 1。
二、填空题
2-1:
enum TrafficLight {
Red,
Yellow,
Green,
}
let light = TrafficLight::Red;
2-2:
enum WebEvent {
PageLoad,
KeyPress(char),
Click { x: i32, y: i32 },
}
let key = WebEvent::KeyPress('a');
2-3:
let v2: Option<i32> = None;
let val2 = match v2 {
Some(n) => n,
None => 0,
};
2-4:
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(a / b)
}
}
match result2 {
Ok(val) => println!("结果: {}", val),
Err(e) => println!("错误: {}", e),
}
2-5:
fn call(&self) {
match self {
Message::Quit => println!("退出"),
Message::Write(text) => println!("写入: {}", text),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
}
}
2-6:
if let Some(max) = config_max {
println!("最大值为: {}", max);
}
2-7:
match opt {
Some(Some(inner)) => println!("值是: {}", inner),
Some(None) => println!("内层是 None"),
None => println!("外层是 None"),
}
2-8:
match x {
1 | 2 => println!("是 1 或 2"),
3..=8 => println!("在 3 到 8 之间"),
_ => println!("其他值"),
}
三、修复错误
3-1:Display trait 未实现。修复:
#[derive(Debug)]
enum Status { Active, Inactive }
fn main() {
let s = Status::Active;
println!("{:?}", s); // 使用 Debug 格式
}
或者手动实现 Display。
3-2:Option<i8> 和 i8 不能直接相加。修复:
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y.unwrap_or(0);
println!("{}", sum);
}
或用 match:
let sum = match y {
Some(val) => x + val,
None => x,
};
3-3:match 分支没有绑定内部的值。修复:
match n {
Number::Integer(v) => println!("整数: {}", v),
Number::Float(v) => println!("浮点数: {}", v),
}
3-4:match 不穷尽,缺少 Coin::Dime 分支。Rust 要求 match 覆盖所有可能。修复:
let val = match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
// 或使用 _ => 10
};
3-5:✅ 编译通过。此题是陷阱——实际上能编译通过。if let 不会移动所有权(因为类型实现了 Copy 的话)。在这个例子中 i32 实现了 Copy,Action::Move 中的 x 和 y 是 i32 类型,所以 if let 只做了解构引用,不影响后续使用。
但如果 Action 包含非 Copy 类型(如 String),if let 会部分移动,导致后续不能使用 action。例如:
enum Action { Move(i32, String), Jump }
let action = Action::Move(10, String::from("up"));
if let Action::Move(x, s) = action { ... }
// match action { ... } ← 此时编译错误,因为 String 已被移出
3-6:area(s) 第一次调用后 s 的所有权被移入函数而消费,第二次调用时 s 已失效。修复:
- 将
area改为area(shape: &Shape) -> f64(借用),调用时传&s - 或在第一次调用前
clone
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle(w, h) => w * h,
}
}
// 调用: area(&s)
3-7:find 返回 Option<&i32>,不是 i32。修复:
let result = numbers.iter().find(|&&x| x == target);
match result {
Some(v) => println!("找到: {}", v),
None => println!("未找到"),
}
3-8:✅ 通过编译。此题是陷阱——实际上编译通过。format! 返回 String,name 也是 String,返回类型一致。match 的两个分支都返回 String,类型匹配。
四、编程题
4-1:
enum FsNode {
File { name: String, size: u64 },
Directory { name: String, children: Vec<FsNode> },
}
impl FsNode {
fn name(&self) -> &str {
match self {
FsNode::File { name, .. } => name,
FsNode::Directory { name, .. } => name,
}
}
fn total_size(&self) -> u64 {
match self {
FsNode::File { size, .. } => *size,
FsNode::Directory { children, .. } => {
children.iter().map(|c| c.total_size()).sum()
}
}
}
fn print_tree(&self, depth: usize) {
let indent = " ".repeat(depth);
match self {
FsNode::File { name, size } => {
println!("{}{} ({} 字节)", indent, name, size);
}
FsNode::Directory { name, children } => {
println!("{}{} (目录)", indent, name);
for child in children {
child.print_tree(depth + 1);
}
}
}
}
fn file_count(&self) -> usize {
match self {
FsNode::File { .. } => 1,
FsNode::Directory { children, .. } => {
children.iter().map(|c| c.file_count()).sum()
}
}
}
}
4-2:
enum Operation {
Add(i32, i32),
Subtract(i32, i32),
Multiply(i32, i32),
Divide(i32, i32),
}
impl Operation {
fn execute(&self) -> Result<f64, String> {
match self {
Operation::Add(a, b) => Ok((a + b) as f64),
Operation::Subtract(a, b) => Ok((a - b) as f64),
Operation::Multiply(a, b) => Ok((a * b) as f64),
Operation::Divide(a, b) => {
if *b == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(*a as f64 / *b as f64)
}
}
}
}
fn symbol(&self) -> &str {
match self {
Operation::Add(..) => "+",
Operation::Subtract(..) => "-",
Operation::Multiply(..) => "*",
Operation::Divide(..) => "/",
}
}
fn describe(&self) -> String {
match self {
Operation::Add(a, b) => format!("{} + {}", a, b),
Operation::Subtract(a, b) => format!("{} - {}", a, b),
Operation::Multiply(a, b) => format!("{} * {}", a, b),
Operation::Divide(a, b) => format!("{} / {}", a, b),
}
}
}
4-3:
enum JsonValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}
impl JsonValue {
fn type_name(&self) -> &str {
match self {
JsonValue::Null => "Null",
JsonValue::Bool(_) => "Bool",
JsonValue::Number(_) => "Number",
JsonValue::String(_) => "String",
JsonValue::Array(_) => "Array",
JsonValue::Object(_) => "Object",
}
}
fn to_string(&self) -> String {
match self {
JsonValue::Null => String::from("null"),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => {
if *n == n.trunc() {
format!("{}", *n as i64)
} else {
n.to_string()
}
}
JsonValue::String(s) => format!("\"{}\"", s),
JsonValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
format!("[{}]", items.join(", "))
}
JsonValue::Object(pairs) => {
let items: Vec<String> = pairs
.iter()
.map(|(k, v)| format!(" {}: {}", k, v.to_string()))
.collect();
format!("{{\n{}\n}}", items.join(",\n"))
}
}
}
}
4-4:
enum OrderState {
Pending,
Paid { amount: f64 },
Shipped { tracking_id: String },
Delivered,
Cancelled { reason: String },
}
impl OrderState {
fn can_cancel(&self) -> bool {
matches!(self, OrderState::Pending | OrderState::Paid { .. })
}
fn pay(self, amount: f64) -> Result<OrderState, String> {
match self {
OrderState::Pending => Ok(OrderState::Paid { amount }),
_ => Err(format!("当前状态 {} 不允许支付", self.describe())),
}
}
fn ship(self, tracking_id: String) -> Result<OrderState, String> {
match self {
OrderState::Paid { .. } => Ok(OrderState::Shipped { tracking_id }),
_ => Err(format!("当前状态 {} 不允许发货", self.describe())),
}
}
fn deliver(self) -> Result<OrderState, String> {
match self {
OrderState::Shipped { .. } => Ok(OrderState::Delivered),
_ => Err(format!("当前状态 {} 不允许签收", self.describe())),
}
}
fn cancel(self, reason: String) -> Result<OrderState, String> {
match self {
OrderState::Pending | OrderState::Paid { .. } => {
Ok(OrderState::Cancelled { reason })
}
_ => Err(format!("当前状态 {} 不允许取消", self.describe())),
}
}
fn describe(&self) -> String {
match self {
OrderState::Pending => String::from("待支付"),
OrderState::Paid { amount } => format!("已支付 {} 元", amount),
OrderState::Shipped { tracking_id } => format!("已发货 ({})", tracking_id),
OrderState::Delivered => String::from("已签收"),
OrderState::Cancelled { reason } => format!("已取消 ({})", reason),
}
}
}
4-5:
enum Expr {
Number(f64),
Add(Box<Expr>, Box<Expr>),
Subtract(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
Divide(Box<Expr>, Box<Expr>),
Negate(Box<Expr>),
}
impl Expr {
fn eval(&self) -> Result<f64, String> {
match self {
Expr::Number(n) => Ok(*n),
Expr::Add(a, b) => Ok(a.eval()? + b.eval()?),
Expr::Subtract(a, b) => Ok(a.eval()? - b.eval()?),
Expr::Multiply(a, b) => Ok(a.eval()? * b.eval()?),
Expr::Divide(a, b) => {
let divisor = b.eval()?;
if divisor == 0.0 {
Err(String::from("除数不能为零"))
} else {
Ok(a.eval()? / divisor)
}
}
Expr::Negate(a) => Ok(-a.eval()?),
}
}
fn to_string(&self) -> String {
match self {
Expr::Number(n) => n.to_string(),
Expr::Add(a, b) => format!("({} + {})", a.to_string(), b.to_string()),
Expr::Subtract(a, b) => format!("({} - {})", a.to_string(), b.to_string()),
Expr::Multiply(a, b) => format!("({} * {})", a.to_string(), b.to_string()),
Expr::Divide(a, b) => format!("({} / {})", a.to_string(), b.to_string()),
Expr::Negate(a) => format!("(-{})", a.to_string()),
}
}
}
五、综合思考题
5-1:
some_u8_value为3u8,匹配到2 | 3 | 4 =>分支,输出 "two, three, or four"。|模式(or pattern):适用场景是多个离散值共享同一段处理逻辑,如1 | 2 | 3。..=范围模式(range pattern):适用场景是连续的数值范围,如1..=100。只能在char和数值类型上使用。
5-2:
- 场景 A:使用
match。需要穷尽所有分支并针对每种情况返回值,match最清晰且编译器会检查穷尽性。 - 场景 B:使用
if let。if let Some(val) = opt { println!("{}", val); } - 场景 C:使用
matches!宏(单次布尔判断最简洁)。if matches!(x, Some(n) if n > 3) { println!("满足条件"); } - 选型总结:
match:需要穷尽所有模式,或处理多种不同模式时if let:只关心一种匹配,其余情况忽略matches!:只需要一个布尔判断结果,不需要提取内部值
5-3:
- 适用场景:
Option<T>:值可能不存在,但不存在不是"错误"。例如:查找元素可能找不到、配置项可能未设置。Result<T, E>:操作可能失败,失败时需要知道原因。例如:文件读取、网络请求、解析数据等 I/O 操作。
Option<T>转Result<T, E>:let opt = Some(42); let result = opt.ok_or("值不存在"); // result: Result<i32, &str> = Ok(42) let opt: Option<i32> = None; let result = opt.ok_or("值不存在"); // result: Result<i32, &str> = Err("值不存在")Result<T, E>转Option<T>:let res: Result<i32, &str> = Ok(42); let opt = res.ok(); // opt: Option<i32> = Some(42) let res: Result<i32, &str> = Err("出错"); let opt = res.ok(); // opt: Option<i32> = None(丢弃错误信息)
5-4:
- 第一次
match时:Message::Write(s)分支将msg中的String绑定到s,值被移动Message::Quit分支没有内部数据,不会移动- 无论匹配到哪个分支,
match会获取所有权
- 取消注释
println!("{:?}", msg)后:编译错误,因为msg的所有权已在第一次 match 中被消费(特别是当匹配到 Write 分支时 String 被移出)。 - Rust 需要 match 的所有权机制:因为 match 分支可能需要将枚举内部的数据绑定到变量并移动出来。如果允许 match 后继续使用,枚举内部可能已被部分移动。解决方案是使用
match &msg借用来只做模式匹配而不移动所有权。 - 正确写法(不使用所有权):
match &msg { Message::Write(s) => println!("写入: {}", s), Message::Quit => println!("退出"), } // msg 仍然可用
5-5:
- "没有中间名"的表示:
- 设计 A:空字符串
""(特殊值表示"无") - 设计 B:
None
- 设计 A:空字符串
- 设计 B 更符合 Rust 惯用风格。原因:
- 类型系统明确区分"有值"和"无值",编译器强制检查,避免空字符串被误用为有效数据
- 避免了"特殊值"的含义歧义(
""到底是"没有中间名"还是"中间名叫空字符串"?) - 调用方必须显式处理
None情况,减少空值错误
- 区分"未提供"和"明确为空"的方案——定义自定义枚举:
这样在语义上可以区分三种状态:未提供(表单没填这个字段)、明确为空(用户选了"我没有中间名")、有值。enum Maybe<T> { NotProvided, // 未提供 Empty, // 明确为空 Value(T), // 有值 } struct User { name: String, middle_name: Maybe<String>, }