章节目录

Option、Result 与错误边界

本章目标

这一章学习 Rust 如何表达“可能没有”和“可能失败”。你会理解 Option<T>Result<T, E>? 运算符,以及为什么错误边界是工程架构的重要组成部分。

它是什么

Option<T> 表示一个值可能存在,也可能不存在:

let maybe_name: Option<&str> = Some("RinBlog");
let nothing: Option<&str> = None;

Result<T, E> 表示一个操作可能成功,也可能失败:

let text: std::io::Result<String> = std::fs::read_to_string("docs/00-preface.md");

成功时是 Ok(T),失败时是 Err(E)

为什么需要

很多语言用 null 表示没有,用异常表示失败。问题是调用者可能忘记处理。Rust 把这两件事放进类型里:如果函数返回 OptionResult,你就必须面对它。

当前项目中,读取 Markdown 文件会失败,解析请求可能失败,查找章节可能不存在。这些情况都应该被显式表达。

怎么使用

常见写法:

fn first_word(input: &str) -> Option<&str> {
    input.split_whitespace().next()
}

fn read_doc() -> std::io::Result<String> {
    let text = std::fs::read_to_string("docs/00-preface.md")?;
    Ok(text)
}

? 的意思是:如果是 Ok,取出里面的值继续;如果是 Err,立刻把错误返回给调用者。

逐行解释

fn chapter_title(slug: &str) -> Option<&'static str> {
    if slug == "00-preface" {
        Some("序章")
    } else {
        None
    }
}
  • fn chapter_title 定义函数。
  • slug: &str 表示借用一个路径标识。
  • -> Option<&'static str> 表示可能返回一个静态字符串,也可能没有。
  • if slug == "00-preface" 判断是否命中。
  • Some("序章") 表示找到了值。
  • None 表示没有对应章节。

调用者不能直接把 Option 当字符串用,必须匹配、解包或继续传播。

常见坑

  • unwrap() 会在没有值或失败时 panic。学习时可以快速验证,工程里要谨慎。
  • ? 只能用在返回 ResultOption 或兼容类型的函数中。
  • 不要把所有错误都吞掉变成默认值,否则调试会困难。
  • Option 表示“没有是正常情况”,Result 表示“失败需要解释原因”。

练习

  1. 写一个函数,从 Vec<String> 里取第一个标题,返回 Option<&String>
  2. 写一个函数读取不存在的文件,打印错误。
  3. unwrap() 改成 match,分别处理成功和失败。
  4. ? 串联两个文件读取操作。

造轮子任务

写一个迷你配置读取器:函数 read_port(path: &str) -> std::io::Result<u16> 读取文件内容并解析端口。如果文件读取失败,返回 IO 错误;如果解析失败,先返回一个自定义的默认端口 7878。重点是分清哪些失败要传播,哪些失败可以降级。

小结

OptionResult 让不确定性进入类型系统。它们不是语法负担,而是工程边界:你必须说明没有值怎么办,失败怎么办,错误在哪里被处理。