bigywater.tech
Rust · 71

Result 错误传播

? 让 Rust 的错误处理简洁,但每一个细节都有规则:什么函数里才能用、错误类型怎么转换、Option 与 Result 能不能混用、main 里出错会怎样。这篇每一条规则都用编译与运行结果验证。

E0277
? 用在返回 () 的函数里,或错误类型无法转换时的错误码
0 次
成功路径上 From::from 的调用次数
1
main 返回 Err 时的进程退出码

传播的行为

实测 · 本机执行输出
  源码:
      1 | use std::num::ParseIntError;
      2 | fn parse_sum(a: &str, b: &str) -> Result<i32, ParseIntError> {
      3 |     println!("  解析 a = {:?}", a);
      4 |     let x: i32 = a.parse()?;
      5 |     println!("  解析 b = {:?}", b);
      6 |     let y: i32 = b.parse()?;
      7 |     println!("  两个都成功,相加");
      8 |     Ok(x + y)
      9 | }
     10 | fn main() {
     11 |     println!("成功: {:?}", parse_sum("3", "4"));
     12 |     println!("第二个失败: {:?}", parse_sum("3", "x"));
     13 |     println!("第一个失败: {:?}", parse_sum("", "4"));
     14 | }
  编译:成功
  运行(退出码 0):
      解析 a = "3"
      解析 b = "4"
      两个都成功,相加
    成功: Ok(7)
      解析 a = "3"
      解析 b = "x"
    第二个失败: Err(ParseIntError { kind: InvalidDigit })
      解析 a = ""
    第一个失败: Err(ParseIntError { kind: Empty })

只能用在 Result / Option 的函数里

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let n: i32 = "5".parse()?;
      3 |     println!("{}", n);
      4 | }
  编译:失败
    error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
     --> <实验目录>/src/u71b.rs:2:29
      |
    1 | fn main() {
      | --------- this function should return `Result` or `Option` to accept `?`
    2 |     let n: i32 = "5".parse()?;
      |                             ^ cannot use the `?` operator in a function that returns `()`
      |
    help: consider adding return type
      |
    1 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
    2 |     let n: i32 = "5".parse()?;
    3 |     println!("{}", n);
    4 +     Ok(())
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

错误类型转换

实测 · 本机执行输出
  源码:
      1 | use std::num::ParseIntError;
      2 | #[allow(dead_code)] #[derive(Debug)] enum MyErr { Parse(ParseIntError), Neg }
      3 | fn f(s: &str) -> Result<i32, MyErr> {
      4 |     let n = s.parse::<i32>()?;
      5 |     if n < 0 { return Err(MyErr::Neg); }
      6 |     Ok(n)
      7 | }
      8 | fn main() { println!("{:?}", f("1")); }
  编译:失败
    error[E0277]: `?` couldn't convert the error to `MyErr`
     --> <实验目录>/src/u71c.rs:4:29
      |
    3 | fn f(s: &str) -> Result<i32, MyErr> {
      |                  ------------------ expected `MyErr` because of this
    4 |     let n = s.parse::<i32>()?;
      |               --------------^ the trait `From<ParseIntError>` is not implemented for `MyErr`
      |               |
      |               this can't be annotated with `?` because it has type `Result<_, ParseIntError>`
      |
    note: `MyErr` needs to implement `From<ParseIntError>`
     --> <实验目录>/src/u71c.rs:2:38
      |
    2 | #[allow(dead_code)] #[derive(Debug)] enum MyErr { Parse(ParseIntError), Neg }
      |                                      ^^^^^^^^^^
      = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.
  编译:成功
  运行(退出码 0):
    Ok(7)
    Err(Neg)
      From<ParseIntError> 被调用
    Err(Parse(ParseIntError { kind: InvalidDigit }))

展开成什么代码见? 运算符展开过程。

Option 与 Result

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    None Some(2) None
    Err("空切片") Ok(9)
  源码:
      1 | fn f(v: &[i32]) -> Result<i32, String> {
      2 |     let x = v.first()?;
      3 |     Ok(*x)
      4 | }
      5 | fn main() { println!("{:?}", f(&[1])); }
  编译:失败
    error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result`
     --> <实验目录>/src/u71f.rs:2:22
      |
    1 | fn f(v: &[i32]) -> Result<i32, String> {
      | -------------------------------------- this function returns a `Result`
    2 |     let x = v.first()?;
      |                      ^ use `.ok_or(...)?` to provide an error compatible with `Result<i32, String>`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

Option 与 Result 的基础见Option 与 Result。

main 返回 Result

实测 · 本机执行输出
  源码:
      1 | fn main() -> Result<(), std::num::ParseIntError> {
      2 |     println!("开始");
      3 |     let n: i32 = "abc".parse()?;
      4 |     println!("不会执行到这里 {}", n);
      5 |     Ok(())
      6 | }
  编译:成功
  运行(退出码 1):
    开始
    Error: ParseIntError { kind: InvalidDigit }

自定义错误类型的写法见自定义错误类型。

分步演示

? 传播
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:在闭包里使用 ?、Box 作为错误类型(放在后面的文章)、? 用在 async 函数里、Try trait(不稳定)、main 返回 ExitCode 的写法
    京ICP备2026067502号-1京公网安备11010502063487号