bigywater.tech
Rust · 10

Option 与 Result

Option 与 Result 是 Rust 处理「可能没有」和「可能失败」的方式。这篇从最底层讲起:它们只是普通枚举,没有魔法;编译器怎样拒绝把 Option 当值用;一组常用方法实际怎么工作;panic 时信息长什么样;以及它们占多少内存 —— 为什么 Option<&T> 是免费的。

E0369
Option + 1 的错误(没有 null,必须显式处理)
8 = 8
Option<&u64> 与 &u64 同为 8 字节(niche 优化)
16 字节
Option:所有位模式都合法,需要标签

两个普通的枚举

实测 · 本机执行输出
  源码:
      1 | #[allow(dead_code)]
      2 | enum MyOption<T> { None, Some(T) }
      3 | #[allow(dead_code)]
      4 | enum MyResult<T, E> { Ok(T), Err(E) }
      5 | fn first_even(v: &[i32]) -> MyOption<i32> { for &x in v { if x % 2 == 0 { return MyOption::Some(x); } } MyOption::None }
      6 | fn main() {
      7 |     match first_even(&[1, 3, 4, 5]) { MyOption::Some(x) => println!("找到偶数 {x}"), MyOption::None => println!("没有偶数") }
      8 |     // 标准库的版本
      9 |     let o: Option<i32> = [1, 3, 4, 5].iter().copied().find(|x| x % 2 == 0);
     10 |     let e: Result<i32, std::num::ParseIntError> = "12x".parse::<i32>();
     11 |     println!("{:?}  {:?}", o, e);
     12 | }
  编译:成功
  运行(退出码 0):
    找到偶数 4
    Some(4)  Err(ParseIntError { kind: InvalidDigit })

没有 null

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let a: Option<i32> = Some(5);
      3 |     let b = a + 1;
      4 |     println!("{b}");
      5 | }
  编译:失败
    error[E0369]: cannot add `{integer}` to `Option<i32>`
       --> <实验目录>/src/u10b.rs:3:15
        |
      3 |     let b = a + 1;
        |             - ^ - {integer}
        |             |
        |             Option<i32>
        |
    note: `Option<i32>` does not implement `Add<{integer}>`
       --> <rust 标准库源码>/library/core/src/option.rs:598:1
        |
    598 | pub enum Option<T> {
        | ^^^^^^^^^^^^^^^^^^ `Option<i32>` is defined in another crate
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0369`.

常用方法

实测 · 本机执行输出
  源码:
      1 | fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> { let n: i32 = s.parse()?; Ok(n * 2) }
      2 | fn main() {
      3 |     let a = Some(3); let n: Option<i32> = None;
      4 |     println!("unwrap_or:{} {}", a.unwrap_or(0), n.unwrap_or(0));
      5 |     println!("map:{:?} {:?}", a.map(|x| x * 10), n.map(|x| x * 10));
      6 |     println!("and_then:{:?}", a.and_then(|x| if x > 2 { Some(x + 100) } else { None }));
      7 |     println!("ok_or:{:?} {:?}", a.ok_or("没有值"), n.ok_or("没有值"));
      8 |     println!("Result 转 Option:{:?} {:?}", "7".parse::<i32>().ok(), "x".parse::<i32>().ok());
      9 |     println!("?:{:?}  {:?}", parse_and_double("21"), parse_and_double("abc"));
     10 |     println!("filter:{:?} {:?}", a.filter(|x| x % 2 == 1), a.filter(|x| x % 2 == 0));
     11 |     println!("take:{:?}", { let mut t = Some(9); let old = t.take(); (old, t) });
     12 | }
  编译:成功
  运行(退出码 0):
    unwrap_or:3 0
    map:Some(30) None
    and_then:Some(103)
    ok_or:Ok(3) Err("没有值")
    Result 转 Option:Some(7) None
    ?:Ok(42)  Err(ParseIntError { kind: InvalidDigit })
    filter:Some(3) None
    take:(Some(9), None)

? 的展开方式见? 运算符展开过程;错误传播见Result 错误传播。

unwrap 与 expect

实测 · 本机执行输出
  编译:成功
  运行(退出码 101):
    先尝试 expect:
    
    thread 'main' (6938666) panicked at <实验目录>/src/u10d.rs:4:15:
    配置里的端口必须是整数: ParseIntError { kind: InvalidDigit }
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

什么时候该 panic、什么时候该返回 Result,见panic!、unwrap 与 expect。

内存占用

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    &u64 = 8,Option<&u64> = 8
    Box<u64> = 8,Option<Box<u64>> = 8
    i32 = 4,Option<i32> = 8
    u64 = 8,Option<u64> = 16
    Result<u8, u8> = 2,Result<u32, String> = 24
    String = 24,Option<String> = 24

niche 优化的原理见Option<&T> 的空指针优化。

分步演示

Option 与 Result
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。大小为 64 位目标、rustc 1.98.1 的结果(niche 优化是编译器的优化,不是语言规范保证的)
    • 没有验证:Option 等的大小、Result<(), Box> 的布局、unwrap_or_else 与 unwrap_or 的求值差别、Option 的 Ord 与排序规则
    京ICP备2026067502号-1京公网安备11010502063487号