bigywater.tech
Rust · 72

? 运算符展开过程

? 只有一个字符,背后是什么?这篇不靠背诵,而是让编译器打印出 HIR,再手写等价的 match 对比,最后验证闭包里的 ? 与 Box 的行为。

2 个分支
? 展开成的 match 分支:Continue 取值、Break 提前 return
1 次
失败路径上 From::from 的调用次数
0 次
成功路径上 From::from 的调用次数

Result 上的展开

实测 · 本机执行输出
  源码:
    pub fn double(s: &str) -> Result<i32, std::num::ParseIntError> {
        let n: i32 = s.parse()?;
        Ok(n * 2)
    }
  HIR:
    fn double(s: &'_ str)
        ->
            Result<i32,
            std::num::ParseIntError> {
        let n: i32 =
            match branch(s.parse()) {
                Break {  0: residual } => #[allow(unreachable_code)]
                    return from_residual(residual),
                Continue {  0: val } => #[allow(unreachable_code)]
                    val,
            };
        Ok(n * 2)
    }

? 的使用规则见Result 错误传播。展开使用 RUSTC_BOOTSTRAP=1,输出格式随编译器版本变化。

Option 上的展开

实测 · 本机执行输出
  源码:
    pub fn first_plus(v: &[i32]) -> Option<i32> {
        let f = v.first()?;
        Some(*f + 1)
    }
  HIR:
    fn first_plus(v: &'_ [i32])
        ->
            Option<i32> {
        let f =
            match branch(v.first()) {
                Break {  0: residual } => #[allow(unreachable_code)]
                    return from_residual(residual),
                Continue {  0: val } => #[allow(unreachable_code)]
                    val,
            };
        Some(*f + 1)
    }

手写展开

实测 · 本机执行输出
  源码:
      1 | #[allow(dead_code)] #[derive(Debug)] struct E(String);
      2 | impl From<std::num::ParseIntError> for E { fn from(e: std::num::ParseIntError) -> E { println!("  From 转换"); E(e.to_string()) } }
      3 | fn with_q(s: &str) -> Result<i32, E> { let n: i32 = s.parse::<i32>()?; Ok(n + 1) }
      4 | fn manual(s: &str) -> Result<i32, E> {
      5 |     let n = match s.parse::<i32>() { Ok(v) => v, Err(e) => return Err(From::from(e)) };
      6 |     Ok(n + 1)
      7 | }
      8 | fn main() {
      9 |     for s in ["41", "zz"] { println!("? 版本 {:?} -> {:?}", s, with_q(s)); println!("手写版本 {:?} -> {:?}", s, manual(s)); }
     10 | }
  编译:成功
  运行(退出码 0):
    ? 版本 "41" -> Ok(42)
    手写版本 "41" -> Ok(42)
      From 转换
    ? 版本 "zz" -> Err(E("invalid digit found in string"))
      From 转换
    手写版本 "zz" -> Err(E("invalid digit found in string"))

闭包里的 ?

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    闭包里 ? 失败: Err(ParseIntError { kind: InvalidDigit })
    outer 继续往下执行
    outer 返回 7

Box

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    "5" -> Ok(5)
    "x" -> Err(invalid digit found in string)
    "-1" -> Err(负数不允许)

自定义错误类型见自定义错误类型。

分步演示

? 的展开
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。HIR 用 RUSTC_BOOTSTRAP=1 -Z unpretty=hir
    • 没有验证:branch / from_residual 在标准库里的具体实现(只从行为上确认 From 被调用,没读源码)、Try trait 自定义实现(不稳定)、MIR 层的形态、? 在 async 块里的展开
    京ICP备2026067502号-1京公网安备11010502063487号