# Rust 枚举(Enum)练习题 > 建议先手动写出每道题的答案,再运行代码验证。 ## 目录 - [一、基础题:判断正误](#一基础题判断正误) - [二、填空题:补充代码](#二填空题补充代码) - [三、找出并修复错误](#三找出并修复错误) - [四、编程题](#四编程题) - [五、综合思考题](#五综合思考题) - [参考答案](#参考答案) *** ## 一、基础题:判断正误 判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。 ### 题目 1-1 ```rust enum Direction { North, South, East, West, } fn main() { let d = Direction::North; println!("{:?}", d); } ``` > 不能,缺少#\[derive(Debug)] ### 题目 1-2 ```rust 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 ```rust 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 ```rust fn main() { let some_number = Some(5); let some_string = Some("hello"); let absent_number: Option = None; println!("{:?}", some_number); println!("{:?}", some_string); println!("{:?}", absent_number); } ``` ### 题目 1-5 ```rust fn main() { let x: Option = Some(5); let y = x + 1; println!("{}", y); } ``` ### 题目 1-6 ```rust 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 ```rust 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 ```rust 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:定义简单枚举 ```rust // 填空:定义一个名为 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:带数据的枚举 ```rust // 填空:定义一个名为 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 的使用 ```rust fn main() { let v1 = Some(10); let v2: Option = ________; // 填空:使用 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 的使用 ```rust 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:枚举上的方法 ```rust 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 语法 ```rust 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 处理 ```rust fn main() { let opt: Option> = Some(Some(42)); // 填空:使用模式匹配提取最内层的值 match opt { Some(Some(inner)) => println!("值是: {}", ________), Some(None) => println!("内层是 None"), None => println!("外层是 None"), } // 期望输出:值是: 42 } ``` ### 题目 2-8:match 的多种模式 ```rust fn main() { let x = 3; // 填空:使用 match 的多模式匹配和范围匹配 match x { ________ => println!("是 1 或 2"), 3..=8 => println!("在 3 到 8 之间"), _ => println!("其他值"), } // 期望输出:在 3 到 8 之间 } ``` *** ## 三、找出并修复错误 以下每段代码都有编译错误,请指出错误并写出修正后的代码。 ### 题目 3-1 ```rust enum Status { Active, Inactive, } fn main() { let s = Status::Active; println!("{}", s); } ``` ### 题目 3-2 ```rust fn main() { let x: i8 = 5; let y: Option = Some(5); let sum = x + y; println!("{}", sum); } ``` ### 题目 3-3 ```rust 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 ```rust #[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 ```rust 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 ```rust 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 ```rust 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 ```rust 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:文件系统节点 设计一个枚举表示文件系统中的节点(文件或目录),并实现相应方法。 ```rust // 定义枚举 FsNode // - File: 包含文件名(String)和文件大小(u64) // - Directory: 包含目录名(String)和子节点列表(Vec) // 你的代码:定义 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:计算器 使用枚举实现一个简单的命令式计算器。 ```rust // 定义枚举 Operation // - Add(i32, i32) // - Subtract(i32, i32) // - Multiply(i32, i32) // - Divide(i32, i32) // 你的代码:定义 Operation 枚举 impl Operation { // 1. 执行运算并返回 Result // 除法时检查除数是否为 0 fn execute(&self) -> Result { // 你的代码 } // 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 值类型。 ```rust // 定义枚举 JsonValue // - Null // - Bool(bool) // - Number(f64) // - String(String) // - Array(Vec) // - 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:状态机 使用枚举实现一个简单的订单状态机。 ```rust // 定义枚举 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 // 如果当前状态不是 Pending,返回错误 fn pay(self, amount: f64) -> Result { // 你的代码 } // 3. 发货(Paid -> Shipped) // 返回 Result // 如果当前状态不是 Paid,返回错误 fn ship(self, tracking_id: String) -> Result { // 你的代码 } // 4. 签收(Shipped -> Delivered) // 返回 Result fn deliver(self) -> Result { // 你的代码 } // 5. 取消订单,返回新的状态 fn cancel(self, reason: String) -> Result { // 你的代码 } // 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:表达式求值器 使用枚举定义一个简单的数学表达式树并实现求值。 ```rust // 定义枚举 Expr // - Number(f64): 数字字面量 // - Add(Box, Box): 加法 // - Subtract(Box, Box): 减法 // - Multiply(Box, Box): 乘法 // - Divide(Box, Box): 除法 // - Negate(Box): 取负 // 你的代码:定义 Expr 枚举 impl Expr { // 1. 求值,返回 Result fn eval(&self) -> Result { // 你的代码 } // 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 的 `|` 模式和 `..=` 范围模式分别适用于什么场景? ```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!` 宏。在什么场景下分别应该使用哪种?以下三段代码分别适合用哪种方式改写? ```rust // 场景 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` 和 `Result` 都用于处理"可能存在或不存在"的情况,但语义不同。请说明: 1. `Option` 适用于什么场景?`Result` 适用于什么场景? 2. 如何将 `Option` 转换为 `Result`?(写出一段示例代码) 3. 如何将 `Result` 转换为 `Option`?(写出一段示例代码) ### 题目 5-4 以下代码中,`msg` 在第一次 `match` 后和第二次 `match` 后分别发生了什么?为什么 Rust 需要 match 的所有权机制? ```rust #[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`。分析以下两种设计的优劣: ```rust // 设计 A:C 风格,用特殊值表示"无" struct User { name: String, middle_name: String, // 没有中间名时用空字符串 "" age: Option, // 未知年龄时用 None } // 设计 B:全部使用 Option struct User { name: String, middle_name: Option, age: Option, } ``` 1. `middle_name` 在两种设计中分别如何表示"没有中间名"? 2. 哪种设计更符合 Rust 的惯用风格?为什么? 3. 如果用户同时有 `Option` 和 `Option` 类型的字段,它们各自为 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` 是 Rust 标准库预置的枚举,`Some` 和 `None` 可以直接使用,`Option` 实现了 `Debug`。`None` 需要类型标注因为它无法自动推断 `T` 的类型。 **1-5**:❌ 编译错误。`Option` 和 `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**: ```rust enum TrafficLight { Red, Yellow, Green, } let light = TrafficLight::Red; ``` **2-2**: ```rust enum WebEvent { PageLoad, KeyPress(char), Click { x: i32, y: i32 }, } let key = WebEvent::KeyPress('a'); ``` **2-3**: ```rust let v2: Option = None; let val2 = match v2 { Some(n) => n, None => 0, }; ``` **2-4**: ```rust fn divide(a: i32, b: i32) -> Result { if b == 0 { Err(String::from("除数不能为零")) } else { Ok(a / b) } } match result2 { Ok(val) => println!("结果: {}", val), Err(e) => println!("错误: {}", e), } ``` **2-5**: ```rust fn call(&self) { match self { Message::Quit => println!("退出"), Message::Write(text) => println!("写入: {}", text), Message::Move { x, y } => println!("移动到 ({}, {})", x, y), } } ``` **2-6**: ```rust if let Some(max) = config_max { println!("最大值为: {}", max); } ``` **2-7**: ```rust match opt { Some(Some(inner)) => println!("值是: {}", inner), Some(None) => println!("内层是 None"), None => println!("外层是 None"), } ``` **2-8**: ```rust match x { 1 | 2 => println!("是 1 或 2"), 3..=8 => println!("在 3 到 8 之间"), _ => println!("其他值"), } ``` ### 三、修复错误 **3-1**:`Display` trait 未实现。修复: ```rust #[derive(Debug)] enum Status { Active, Inactive } fn main() { let s = Status::Active; println!("{:?}", s); // 使用 Debug 格式 } ``` 或者手动实现 `Display`。 **3-2**:`Option` 和 `i8` 不能直接相加。修复: ```rust fn main() { let x: i8 = 5; let y: Option = Some(5); let sum = x + y.unwrap_or(0); println!("{}", sum); } ``` 或用 match: ```rust let sum = match y { Some(val) => x + val, None => x, }; ``` **3-3**:match 分支没有绑定内部的值。修复: ```rust match n { Number::Integer(v) => println!("整数: {}", v), Number::Float(v) => println!("浮点数: {}", v), } ``` **3-4**:match 不穷尽,缺少 `Coin::Dime` 分支。Rust 要求 match 覆盖所有可能。修复: ```rust 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。例如: ```rust 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` ```rust 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`。修复: ```rust 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**: ```rust enum FsNode { File { name: String, size: u64 }, Directory { name: String, children: Vec }, } 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**: ```rust enum Operation { Add(i32, i32), Subtract(i32, i32), Multiply(i32, i32), Divide(i32, i32), } impl Operation { fn execute(&self) -> Result { 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**: ```rust enum JsonValue { Null, Bool(bool), Number(f64), String(String), Array(Vec), 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 = arr.iter().map(|v| v.to_string()).collect(); format!("[{}]", items.join(", ")) } JsonValue::Object(pairs) => { let items: Vec = pairs .iter() .map(|(k, v)| format!(" {}: {}", k, v.to_string())) .collect(); format!("{{\n{}\n}}", items.join(",\n")) } } } } ``` **4-4**: ```rust 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 { match self { OrderState::Pending => Ok(OrderState::Paid { amount }), _ => Err(format!("当前状态 {} 不允许支付", self.describe())), } } fn ship(self, tracking_id: String) -> Result { match self { OrderState::Paid { .. } => Ok(OrderState::Shipped { tracking_id }), _ => Err(format!("当前状态 {} 不允许发货", self.describe())), } } fn deliver(self) -> Result { match self { OrderState::Shipped { .. } => Ok(OrderState::Delivered), _ => Err(format!("当前状态 {} 不允许签收", self.describe())), } } fn cancel(self, reason: String) -> Result { 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**: ```rust enum Expr { Number(f64), Add(Box, Box), Subtract(Box, Box), Multiply(Box, Box), Divide(Box, Box), Negate(Box), } impl Expr { fn eval(&self) -> Result { 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`。 ```rust if let Some(val) = opt { println!("{}", val); } ``` - 场景 C:使用 `matches!` 宏(单次布尔判断最简洁)。 ```rust if matches!(x, Some(n) if n > 3) { println!("满足条件"); } ``` - 选型总结: - `match`:需要穷尽所有模式,或处理多种不同模式时 - `if let`:只关心一种匹配,其余情况忽略 - `matches!`:只需要一个布尔判断结果,不需要提取内部值 **5-3**: 1. 适用场景: - `Option`:值**可能不存在**,但不存在不是"错误"。例如:查找元素可能找不到、配置项可能未设置。 - `Result`:操作**可能失败**,失败时需要知道原因。例如:文件读取、网络请求、解析数据等 I/O 操作。 2. `Option` 转 `Result`: ```rust let opt = Some(42); let result = opt.ok_or("值不存在"); // result: Result = Ok(42) let opt: Option = None; let result = opt.ok_or("值不存在"); // result: Result = Err("值不存在") ``` 3. `Result` 转 `Option`: ```rust let res: Result = Ok(42); let opt = res.ok(); // opt: Option = Some(42) let res: Result = Err("出错"); let opt = res.ok(); // opt: Option = 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` 借用来只做模式匹配而不移动所有权。 - 正确写法(不使用所有权): ```rust match &msg { Message::Write(s) => println!("写入: {}", s), Message::Quit => println!("退出"), } // msg 仍然可用 ``` **5-5**: 1. "没有中间名"的表示: - 设计 A:空字符串 `""`(特殊值表示"无") - 设计 B:`None` 2. 设计 B 更符合 Rust 惯用风格。原因: - 类型系统明确区分"有值"和"无值",编译器强制检查,避免空字符串被误用为有效数据 - 避免了"特殊值"的含义歧义(`""` 到底是"没有中间名"还是"中间名叫空字符串"?) - 调用方必须显式处理 `None` 情况,减少空值错误 3. 区分"未提供"和"明确为空"的方案——定义自定义枚举: ```rust enum Maybe { NotProvided, // 未提供 Empty, // 明确为空 Value(T), // 有值 } struct User { name: String, middle_name: Maybe, } ``` 这样在语义上可以区分三种状态:未提供(表单没填这个字段)、明确为空(用户选了"我没有中间名")、有值。