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 把这两件事放进类型里:如果函数返回 Option 或 Result,你就必须面对它。
当前项目中,读取 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。学习时可以快速验证,工程里要谨慎。?只能用在返回Result、Option或兼容类型的函数中。- 不要把所有错误都吞掉变成默认值,否则调试会困难。
Option表示“没有是正常情况”,Result表示“失败需要解释原因”。
练习
- 写一个函数,从
Vec<String>里取第一个标题,返回Option<&String>。 - 写一个函数读取不存在的文件,打印错误。
- 把
unwrap()改成match,分别处理成功和失败。 - 用
?串联两个文件读取操作。
造轮子任务
写一个迷你配置读取器:函数 read_port(path: &str) -> std::io::Result<u16> 读取文件内容并解析端口。如果文件读取失败,返回 IO 错误;如果解析失败,先返回一个自定义的默认端口 7878。重点是分清哪些失败要传播,哪些失败可以降级。
小结
Option 和 Result 让不确定性进入类型系统。它们不是语法负担,而是工程边界:你必须说明没有值怎么办,失败怎么办,错误在哪里被处理。