learning-rust/part5/enum练习题.md

38 KiB
Raw Permalink Blame History

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);
}

不能Option枚举类型不能直接用+需要实现add方法

题目 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));
}

不能match需要匹配所有可能出现的类型

题目 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!("通行"),
    }
    // 期望输出:停止
}
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

fn main() {
    let light = TrafficLight::Red;
    match light {
        TrafficLight::Red => println!("停止"),
        TrafficLight::Yellow => println!("减速"),
        TrafficLight::Green => println!("通行"),
    }
}

题目 2-2带数据的枚举

// 填空:定义一个名为 WebEvent 的枚举
// - PageLoad: 无数据
// - KeyPress: 包含一个 char
// - Click: 包含两个 i32x 和 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");
}
enum WebEvent {
    PageLoad,
    KeyPress(char),
    Click { x: i32, y: i32 },
}

fn main() {
    let load = WebEvent::PageLoad;
    let key = WebEvent::KeyPress('a');
    let click = WebEvent::Click { x: 100, y: 200 };
    println!("ok");
}

题目 2-3Option 的使用

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
}
fn main() {
    let v1 = Some(10);
    let v2: Option<i32> = None;

    let val1 = match v1 {
        Some(n) => n,
        None => 0,
    };
    let val2 = match v2 {
        Some(n) => n,
        None => 0,
    };
    println!("val1: {}, val2: {}", val1, val2);
}

题目 2-4Result 的使用

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
    // 错误: 除数不能为零
}
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("除数不能为零"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result1 = divide(10, 2);
    let result2 = divide(10, 0);

    match result1 {
        Ok(val) => println!("结果: {}", val),
        Err(e) => println!("错误: {}", e),
    }

    match result2 {
        Ok(val) => println!("结果: {}", val),
        Err(e) => println!("错误: {}", e),
    }
}

题目 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)
}
enum Message {
    Quit,
    Write(String),
    Move { x: i32, y: i32 },
}

impl Message {
    fn call(&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();
}

题目 2-6if let 语法

fn main() {
    let config_max = Some(3u8);

    // 填空:使用 if let 简化 match
    ________ = config_max {
        println!("最大值为: {}", max);
    }

    // 等价于以下 match 写法:
    // match config_max {
    //     Some(max) => println!("最大值为: {}", max),
    //     _ => (),
    // }
    // 期望输出:最大值为: 3
}
fn main() {
    let config_max = Some(3u8);

    if let Some(max) = config_max {
        println!("最大值为: {}", max);
    }
}

题目 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
}
fn main() {
    let opt: Option<Option<i32>> = Some(Some(42));

    match opt {
        Some(Some(inner)) => println!("值是: {}", inner),
        Some(None) => println!("内层是 None"),
        None => println!("外层是 None"),
    }
}

题目 2-8match 的多种模式

fn main() {
    let x = 3;

    // 填空:使用 match 的多模式匹配和范围匹配
    match x {
        ________ => println!("是 1 或 2"),
        3..=8 => println!("在 3 到 8 之间"),
        _ => println!("其他值"),
    }
    // 期望输出:在 3 到 8 之间
}
fn main() {
    let x = 3;

    match x {
        1 | 2 => println!("是 1 或 2"),
        3..=8 => println!("在 3 到 8 之间"),
        _ => println!("其他值"),
    }
}

三、找出并修复错误

以下每段代码都有编译错误,请指出错误并写出修正后的代码。

题目 3-1

enum Status {
    Active,
    Inactive,
}

fn main() {
    let s = Status::Active;
    println!("{}", s);
}
#[derive(Debug)]
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-3JSON 值类型

定义一个枚举表示简化的 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

对比 matchif letmatches! 宏。在什么场景下分别应该使用哪种?以下三段代码分别适合用哪种方式改写?

// 场景 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> 都用于处理"可能存在或不存在"的情况,但语义不同。请说明:

  1. Option<T> 适用于什么场景?Result<T, E> 适用于什么场景?
  2. 如何将 Option<T> 转换为 Result<T, E>?(写出一段示例代码)
  3. 如何将 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>。分析以下两种设计的优劣:

// 设计 AC 风格,用特殊值表示"无"
struct User {
    name: String,
    middle_name: String,  // 没有中间名时用空字符串 ""
    age: Option<u32>,     // 未知年龄时用 None
}

// 设计 B全部使用 Option
struct User {
    name: String,
    middle_name: Option<String>,
    age: Option<u32>,
}
  1. middle_name 在两种设计中分别如何表示"没有中间名"
  2. 哪种设计更符合 Rust 的惯用风格?为什么?
  3. 如果用户同时有 Option<String>Option<String> 类型的字段,它们各自为 None 的语义可能不同(如"未提供" vs "明确为空"),你会如何设计来区分这些情况?

参考答案

请独立完成再查看答案。

点击展开答案

一、基础题

1-1 编译错误。枚举变体不会自动实现任何 trait。需要 #[derive(Debug)] 或手动实现 Debug 才能使用 {:?} 打印。

1-2 编译错误。IpAddrKind 没有 derive Debug,所以 {:?} 无法打印 home.kind。需要加上 #[derive(Debug)]

1-3 通过编译。枚举变体可以附带不同类型的数据,此例中 V4V6 都附带 String

1-4 通过编译。Option<T> 是 Rust 标准库预置的枚举,SomeNone 可以直接使用,Option 实现了 DebugNone 需要类型标注因为它无法自动推断 T 的类型。

1-5 编译错误。Option<i32>i32 是不同的类型,不能直接相加。需要先用 match 或 unwrap() 等方法提取值。

1-6 编译错误。match 必须穷尽所有可能exhaustiveCoin 有 4 个变体但只匹配了 3 个,缺少 Coin::Quarter 分支。需要补全或添加 _ => 通配分支。

1-7 通过编译。枚举变体可以使用不同的数据绑定方式:无数据(Quit)、命名结构体式(Move { x, y })、元组式(Write(String)ChangeColor(i32, i32, i32))。

1-8 通过编译。if letmatch 的语法糖,只匹配一种模式并忽略其余。这里匹配失败走 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-1Display trait 未实现。修复:

#[derive(Debug)]
enum Status { Active, Inactive }

fn main() {
    let s = Status::Active;
    println!("{:?}", s);  // 使用 Debug 格式
}

或者手动实现 Display

3-2Option<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-3match 分支没有绑定内部的值。修复:

match n {
    Number::Integer(v) => println!("整数: {}", v),
    Number::Float(v) => println!("浮点数: {}", v),
}

3-4match 不穷尽,缺少 Coin::Dime 分支。Rust 要求 match 覆盖所有可能。修复:

let val = match coin {
    Coin::Penny => 1,
    Coin::Nickel => 5,
    Coin::Dime => 10,
    // 或使用 _ => 10
};

3-5 编译通过。此题是陷阱——实际上能编译通过。if let 不会移动所有权(因为类型实现了 Copy 的话)。在这个例子中 i32 实现了 CopyAction::Move 中的 xy 是 i32 类型,所以 if let 只做了解构引用,不影响后续使用。

但如果 Action 包含非 Copy 类型(如 Stringif 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-6area(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-7find 返回 Option<&i32>,不是 i32。修复:

let result = numbers.iter().find(|&&x| x == target);

match result {
    Some(v) => println!("找到: {}", v),
    None => println!("未找到"),
}

3-8 通过编译。此题是陷阱——实际上编译通过。format! 返回 Stringname 也是 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_value3u8,匹配到 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

  1. 适用场景:
    • Option<T>:值可能不存在,但不存在不是"错误"。例如:查找元素可能找不到、配置项可能未设置。
    • Result<T, E>:操作可能失败,失败时需要知道原因。例如:文件读取、网络请求、解析数据等 I/O 操作。
  2. 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("值不存在")
    
  3. 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

  1. "没有中间名"的表示:
    • 设计 A空字符串 ""(特殊值表示"无"
    • 设计 BNone
  2. 设计 B 更符合 Rust 惯用风格。原因:
    • 类型系统明确区分"有值"和"无值",编译器强制检查,避免空字符串被误用为有效数据
    • 避免了"特殊值"的含义歧义("" 到底是"没有中间名"还是"中间名叫空字符串"
    • 调用方必须显式处理 None 情况,减少空值错误
  3. 区分"未提供"和"明确为空"的方案——定义自定义枚举:
    enum Maybe<T> {
        NotProvided,         // 未提供
        Empty,               // 明确为空
        Value(T),            // 有值
    }
    
    struct User {
        name: String,
        middle_name: Maybe<String>,
    }
    
    这样在语义上可以区分三种状态:未提供(表单没填这个字段)、明确为空(用户选了"我没有中间名")、有值。