Course Builder个人学习工作台 · 本地模式
Rust:从所有权到可测试的命令行工具

08|Result 与问号:把失败交给合适的层

目标:用 Result 返回可预期的失败,并用 ? 向调用者传播错误。

Option 只回答“有没有”。当输入不是一个合法数字、文件打不开时,我们还需要知道失败原因。Result<T, E> 的 Ok(T) 保存成功结果,Err(E) 保存错误。失败也是函数可能返回的值,因此调用者必须选择处理或继续传播。

? 的作用是:遇到成功值就取出它继续执行;遇到错误就从当前函数提前返回错误,必要时通过兼容转换调整错误类型。它不是打印错误,也不是自动重试。包含 ? 的函数需要有相容的返回类型,最常见的是 Result。

从一个数字解析器开始

创建 examples/errors.rs:

use std::num::ParseIntError;

fn doubled_limit(input: &str) -> Result<u16, ParseIntError> {
    let value: u8 = input.parse()?;
    Ok(u16::from(value) * 2)
}

fn main() {
    match doubled_limit("12") {
        Ok(value) => println!("limit={value}"),
        Err(error) => println!("invalid: {error}"),
    }
    match doubled_limit("many") {
        Ok(value) => println!("limit={value}"),
        Err(_) => println!("invalid input"),
    }
}

use 把标准库类型引入当前名字空间。u8 能表示 0–255,u16 能表示 0–65535;先转成 u16 再乘 2,保证所有合法 u8 输入都不会在这次乘法中溢出。parse() 从左侧 u8 推断要解析的目标类型。

运行前先预测:第二次解析失败会不会使整个程序 panic?

cargo run --quiet --example errors

预期输出:

limit=24
invalid input

第一次从 Ok(12) 中取出 12,再返回 Ok(24)。第二次在 ? 处提前返回 Err,由 main 选择输出消息;Err(_) 表示这里暂不使用具体错误值。真实 CLI 会保留文件系统错误原因,让用户知道如何修复。

追踪一个越界值

  1. 将第一次调用中的 "12" 改为 "256"。
  2. 运行后,第一行应进入 invalid: 分支,错误信息指出数值超出目标类型范围;具体措辞由标准库版本决定。
  3. 改为 "255",应输出 limit=510。
  4. 恢复主示例。这区分了“解析范围不合法”与“计算阶段溢出”。

独立练习:写 add_inputs(left: &str, right: &str) -> Result<u16, ParseIntError>。分别把两个输入解析为 u8,转为 u16 后相加。任何一个输入不合法都返回错误。

完整参考实现
use std::num::ParseIntError;

fn add_inputs(left: &str, right: &str) -> Result<u16, ParseIntError> {
    let left: u8 = left.parse()?;
    let right: u8 = right.parse()?;
    Ok(u16::from(left) + u16::from(right))
}

fn main() {
    assert_eq!(add_inputs("2", "3").ok(), Some(5));
    assert_eq!(add_inputs("255", "255").ok(), Some(510));
    assert!(add_inputs("bad", "3").is_err());
    assert!(add_inputs("2", "bad").is_err());
    println!("result checks passed");
}

替换实验文件运行,预期 result checks passed。ok() 将成功值转为 Option,丢弃错误信息,仅用于这里的简短断言;业务边界不要无意丢掉诊断。is_err() 判断是否失败,assert! 要求条件为真。

如果提示 ? 不能用于当前函数,检查函数签名是否仍返回 () 或普通整数。如果提示无法转换错误类型,先比较表达式中的错误类型和函数声明的错误类型。不要为了统一它们立即改成 unwrap();unwrap() 在 Err 时 panic,只适合确有依据的不变量或简短测试。

本课新增 examples/errors.rs。项目读取文件时会使用相同的“底层返回 Result,上层决定如何展示失败”结构。

参考资料