diff --git a/.trae/rules/git-commit-message.md b/.trae/rules/git-commit-message.md new file mode 100644 index 0000000..4d87c50 --- /dev/null +++ b/.trae/rules/git-commit-message.md @@ -0,0 +1,7 @@ +--- +alwaysApply: true +scene: git_message +language: zh-CN +--- + +在此处编写规则,自定义 AI 生成提交信息的风格。 diff --git a/part5/Cargo.lock b/part5/Cargo.lock new file mode 100644 index 0000000..76e9a64 --- /dev/null +++ b/part5/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "part5" +version = "0.1.0" diff --git a/part5/Cargo.toml b/part5/Cargo.toml new file mode 100644 index 0000000..e7a4217 --- /dev/null +++ b/part5/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "part5" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/part5/enum练习题.md b/part5/enum练习题.md new file mode 100644 index 0000000..d93198b --- /dev/null +++ b/part5/enum练习题.md @@ -0,0 +1,1493 @@ +# 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, + } + ``` + 这样在语义上可以区分三种状态:未提供(表单没填这个字段)、明确为空(用户选了"我没有中间名")、有值。 + +
diff --git a/part5/src/main.rs b/part5/src/main.rs new file mode 100644 index 0000000..17b9d02 --- /dev/null +++ b/part5/src/main.rs @@ -0,0 +1,19 @@ +#[derive(Debug)] +enum Direction { + North, + South, + East, + West, +} + +enum IpAddr { + V4(String), + V6(String), +} + +fn main() { + let direction = Direction::North; + println!(" direction: {:?}", direction); + let home = IpAddr::V4(String::from("127.0.0.1")); + // println!(" home: {:?}", home); +}