《Rust快速入门》8. 错误处理

区分 Option、Result、错误传播与 panic;通过完整的端口配置解析程序验证缺省、无效输入和数值边界,并给出练习验收。

错误处理:区分缺省值、输入错误与程序缺陷

一个服务需要读取端口配置:没有配置时使用 8080,配置为 "443" 时接受它,配置为 "abc"、"0" 或超出范围时给出错误。这里有两种不同的问题:有没有值,以及这个值能不能使用。把它们混为一谈,容易把输入错误写成崩溃,或用默认值掩盖错误配置。

本节只使用 Rust 标准库,例子可保存为单个文件。它没有实际绑定网络端口,只验证配置解析;端口冲突、权限和网络监听属于另一个可能失败的步骤。

1. Option 表示可能没有值

Option<T> 有 Some(value) 与 None 两种情况,适合表达配置未提供、查找没有结果等正常缺省。它本身不保存失败原因。

// 说明片段:有值时保留原值,无值时使用默认值。
let provided: Option<&str> = None;
let text = provided.unwrap_or("8080");
assert_eq!(text, "8080");

这里的 unwrap_or 提供默认值,不会因为 None 而 panic。是否允许缺省是业务规则,不能把所有缺失都默认视为错误,也不能把所有错误都转换成缺省。

2. Result 表示操作成功或失败

Result<T, E> 的 Ok(value) 保存成功值,Err(error) 保存失败信息。例如,字符串转整数可能失败,需要由调用者决定怎样报告或处理。

// 说明片段:明确处理两个分支,不因用户输入无效而 panic。
let parsed = "abc".parse::<u16>();
match parsed {
    Ok(port) => println!("解析到端口:{port}"),
    Err(error) => eprintln!("端口不是有效的 u16 整数:{error}"),
}

成功解析为 u16 只说明数值在 0..=65535 范围内。下面的配置约定要求 1..=65535,所以还需要拒绝 0。类型能表达的范围与业务允许的范围不是同一回事。

3. 用 ? 将错误交给调用者

在返回 Result 的普通函数中,? 取得成功值;失败时提前返回错误,必要时通过相应转换将错误类型转成函数的返回类型。它不会自动打印日志、重试或忽略错误。

本例先用 map_err 将解析错误改为稳定的教学提示,再用 ? 返回。为了保持快速入门的范围,错误类型使用 String;如果调用者需要按错误种类重试或定位原因,可改用枚举和 std::error::Error,不要依靠匹配错误文案做业务分支。

4. 完整例子:解析端口并检查九种输入

将以下完整代码保存为 port.rs。只有缺省 None 使用默认值;显式提供空字符串或无效值都返回 Err。

// file: port.rs
fn parse_port(input: Option<&str>) -> Result<u16, String> {
    let text = input.unwrap_or("8080").trim();
    if text.is_empty() {
        return Err("端口为空".to_owned());
    }

    let port = text
        .parse::<u16>()
        .map_err(|_| "端口必须是 1..=65535 的整数".to_owned())?;
    if port == 0 {
        return Err("端口必须是 1..=65535 的整数".to_owned());
    }
    Ok(port)
}

fn main() {
    let invalid = || Err("端口必须是 1..=65535 的整数".to_owned());
    let cases: [(Option<&str>, Result<u16, String>); 9] = [
        (None, Ok(8080)),
        (Some("80"), Ok(80)),
        (Some(" 443 "), Ok(443)),
        (Some(""), Err("端口为空".to_owned())),
        (Some("abc"), invalid()),
        (Some("0"), invalid()),
        (Some("65535"), Ok(65535)),
        (Some("65536"), invalid()),
        (Some("-1"), invalid()),
    ];

    for (input, expected) in cases {
        let actual = parse_port(input);
        assert_eq!(actual, expected);
        println!("{input:?} -> {actual:?}");
    }
}

本地已准备 Rust 工具链时运行:

rustc --edition 2021 port.rs -o port-demo
./port-demo

Windows 可将输出文件命名为 port-demo.exe,随后运行 ./port-demo.exe。代码不依赖第三方 crate;本机未安装 Rust 时,可将上面的完整文件粘贴到 Rust Playground ,选择 Stable 和 Edition 2021 验证。

预期输出如下。2026-10-14 在 Rust Playground 的 Stable / Edition 2021 远程编译运行通过,九个断言及输出均匹配;服务未返回可核对的编译器版本号,本机未复跑。每行输出前的断言还会核对返回值,不能只以程序打印了九行作为成功标准。

None -> Ok(8080)
Some("80") -> Ok(80)
Some(" 443 ") -> Ok(443)
Some("") -> Err("端口为空")
Some("abc") -> Err("端口必须是 1..=65535 的整数")
Some("0") -> Err("端口必须是 1..=65535 的整数")
Some("65535") -> Ok(65535)
Some("65536") -> Err("端口必须是 1..=65535 的整数")
Some("-1") -> Err("端口必须是 1..=65535 的整数")

5. 什么时候使用 panic!、unwrap 与 expect

unwrap 在 Result::Err 或 Option::None 上会 panic;expect 也会 panic,只是提供额外的错误上下文。它们适合某些明确成立的假设或测试断言,不适合代替对外部配置、文件、网络和用户输入的正常错误处理。

panic! 会触发当前执行线程的 panic 路径;按构建策略可能展开栈或终止进程。它不是函数返回的 Err,也不是整个应用的自动恢复机制。不要为了“没有错误返回”而把所有失败改成 panic。

情况本节采用的表达调用者要决定什么
配置未提供None,按约定使用默认值缺省是否被允许
配置合法Ok(port)继续下一步,网络监听仍可能失败
配置空、非法或越界Err(message)报告错误,拒绝启动或让用户重试
已声明假设被破坏可考虑 panic/断言假设是否成立,失败是否确实应停止当前执行

6. 练习与验收

  1. 给上面的用例加入 Some(" "),期望得到“端口为空”,并将数组长度改成 10。这检查的是去除空白之后的输入,而不是只判断原字符串是否为空。
  2. 将 None 的默认值改为 3000,同步修改该用例的期望值;Some("0") 仍必须返回错误。只有缺省值改变,业务范围不改变。
  3. 将参数改为必填:把 None 转成 Err("缺少端口配置")。参考做法是在函数开始使用 input.ok_or_else(|| "缺少端口配置".to_owned())?,再对返回字符串 trim()。对应的 None 测试必须改成错误期望,其他八个用例继续通过。

官方参考与阅读衔接

本节在 2026-10-14 按上述官方文档修订。它修复了“错误处理”页面误放集合内容的问题;集合仍见第 7 篇 。下一步阅读第 9 篇:泛型与特性 ,了解 Result<T, E> 这类类型参数的用法。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「rust」更多文章

  1. 性能剖析与优化
  2. 测试与基准:criterion 与 proptest
  3. Serde 与序列化生态