1. Option 就是 enum

Option<T> 的定义(标准库源码简化):

1
2
3
4
enum Option<T> {
Some(T), // 有值
None, // 无值
}

就是个带泛型的 enum,Some 变体持有数据,None 是单元变体。对应 C++ 的 std::optional<T>

上篇已经讲过 enum 的 match 解构,本篇聚焦如何不用 match 也能优雅操作 Option

2. 解构与提取

match / if let

1
2
3
4
5
6
7
8
9
10
11
12
let opt = Some(42);

// match 穷尽解构
match opt {
Some(v) => println!("{}", v),
None => println!("empty"),
}

// if let 单分支
if let Some(v) = opt {
println!("{}", v);
}

这两种方式在上一篇文章中已经介绍。它们适用于需要分别处理两种情况的场景。但很多时候你只想做一件事——取值、转换、或给默认值——这时组合子比 match 更简洁。

unwrap / expect

直接取走 Some 内部的值,None 时 panic:

1
2
let v = opt.unwrap();        // None 时 panic,无自定义消息
let v = opt.expect("必须存在"); // None 时 panic,带自定义消息

使用原则: unwrap / expect 只在你能逻辑上保证值一定存在时用——例如刚创建、刚 insert 完、或程序继续下去也没意义的情况。其余场景优先用组合子。

3. 按需求选组合子

3.1 要转换值 → map / map_or / map_or_else

1
2
3
4
5
6
7
8
9
10
let opt = Some("hello");

// map: 有值则转换,无值保持 None
let len = opt.map(|s| s.len()); // Some(5)

// map_or: 有值转换,无值给默认
let len = opt.map_or(0, |s| s.len()); // 5(如果是 None 则返回 0)

// map_or_else: 同上,但默认值通过闭包延迟计算(避免不必要的开销)
let len = opt.map_or_else(|| 0, |s| s.len());

实际场景——解析环境变量:

1
2
3
4
5
let port = std::env::var("PORT")
.ok() // Result → Option
.map(|s| s.parse::<u16>()) // Option<String> → Option<Result<u16, _>>
.and_then(|r| r.ok()) // 展平 Result(见 3.2)
.map_or(8080, |p| p); // 解析失败或未设则默认 8080

3.2 要串联可能失败的操作 → and_then

map 的闭包返回普通值;and_then(等价于 flatmap / bind)的闭包返回 Option,避免嵌套 Option<Option<T>> 的问题。

1
2
3
4
5
6
7
8
9
10
11
// 场景:查缓存 → 缓存未命中则查数据库
fn get_from_cache(key: &str) -> Option<Data> { /* ... */ }
fn get_from_db(key: &str) -> Option<Data> { /* ... */ }

// 错误写法:map 导致嵌套 Option<Option<Data>>
let result = get_from_cache("user:42").map(|data| get_from_db(&data.next_key));
// 类型:Option<Option<Data>>,需要 unwrap 两次

// 正确写法:and_then 自动展平
let result = get_from_cache("user:42").and_then(|data| get_from_db(&data.next_key));
// 类型:Option<Data>

多级串联:

1
2
3
4
let config = std::env::var("CONFIG_FILE")
.ok() // Result → Option
.and_then(|path| std::fs::read_to_string(path).ok()) // 读文件,失败 → None
.and_then(|content| parse_config(&content)); // 解析,失败 → None

链条中任一步返回 None,整条链短路到 None

3.3 要给默认值 → unwrap_or / unwrap_or_else

1
2
3
4
5
let opt: Option<&str> = None;

let val = opt.unwrap_or("default"); // "default"
let val = opt.unwrap_or_else(|| expensive_fallback());
// 延迟计算:只在 None 时才调用闭包

unwrap_or_else 而不是 unwrap_or 的场景:

1
2
3
4
5
// unwrap_or 的参数总是求值——即便 opt 是 Some,default_config() 也会执行
let config = opt.unwrap_or(default_config()); // 浪费

// unwrap_or_else 的闭包只在 None 时调用
let config = opt.unwrap_or_else(|| default_config());

3.4 要过滤条件 → filter

1
2
3
4
let opt = Some(42);

let even = opt.filter(|&n| n % 2 == 0); // Some(42)
let odd = opt.filter(|&n| n % 2 != 0); // None

实际场景——只处理”有意义”的值:

1
2
3
let name = get_name()
.filter(|n| !n.is_empty()) // 空字符串视为无值
.unwrap_or_else(|| "Unknown".into());

3.5 要判断存在性 → is_some / is_none

1
2
if opt.is_some() { /* 有值 */ }
if opt.is_none() { /* 无值 */ }

简单判断不需要 match 或组合子。只做判断、不取值的场景最直接。

3.6 组合子速查表

需求 组合子 签名要点
转换值 map Option<T> → Option<U>,闭包返回 U
转换值 + 默认 map_or map + 默认值
转换值 + 延迟默认 map_or_else map + 闭包默认值
串联操作 and_then 闭包返回 Option<U>,自动展平
给默认值 unwrap_or 直接给值
延迟默认值 unwrap_or_else None 时才计算的闭包
过滤 filter 不满足条件 → None
带所有权替换 replace opt.replace(v) 返回旧值,放入新值
取出并留 None take 取走所有权,原地留 None
惰性链入 or / or_else None 时用备选 Option

4. Option ↔ Result 互转

实际代码中 Option 和 Result 经常共存——函数返回 Result,内部操作产出的却是 Option

4.1 Result → Option:ok()

1
2
3
4
5
let res: Result<i32, &str> = Ok(42);
let opt = res.ok(); // Some(42)

let res: Result<i32, &str> = Err("fail");
let opt = res.ok(); // None —— 错误信息被丢弃
1
2
let res: Result<i32, &str> = Err("fail");
let opt = res.err(); // Some("fail") —— 只取错误

4.2 Option → Result:ok_or / ok_or_else

1
2
3
4
5
6
7
8
let opt = Some(42);
let res: Result<i32, &str> = opt.ok_or("not found"); // Ok(42)

let opt: Option<i32> = None;
let res: Result<i32, &str> = opt.ok_or("not found"); // Err("not found")

// 延迟构造错误消息
let res = opt.ok_or_else(|| format!("key {} not found", key));

4.3 ? 运算符与 Option

? 对 Option 同样有效——在返回 Option 的函数中遇到 None 则提前返回:

1
2
3
4
5
fn parse_two_numbers(a: &str, b: &str) -> Option<(i32, i32)> {
let x = a.parse::<i32>().ok()?; // 解析失败 → 返回 None
let y = b.parse::<i32>().ok()?;
Some((x, y))
}

典型模式:返回 Result 的函数中用 ok_or 把 Option 转成 Result,再用 ? 传播:

1
2
3
4
5
6
fn get_config(key: &str) -> Result<String, Box<dyn std::error::Error>> {
std::env::var(key)
.ok() // Result → Option
.filter(|v| !v.is_empty()) // 空值视为未设置
.ok_or_else(|| format!("{} not set", key).into()) // Option → Result
}

5. 与 C++ std::optional 对比

C++17 引入了 std::optional<T>,对应 Option<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <optional>

std::optional<int> opt = 42;

// 取值(空时行为未定义)
int v = *opt;
int v = opt.value(); // 空时抛 std::bad_optional_access

// 给默认值
int v = opt.value_or(0);

// 判断
if (opt.has_value()) { /* ... */ }

对比:

C++ std::optional Rust Option
定义 std::optional<T> Option<T>(就是 enum)
有值构造 std::optional<int>(42)42(隐式) Some(42)
空值 std::nullopt None
默认值 value_or(0) unwrap_or(0)
转换 ❌ 没有 map / and_then map / and_then / map_or
链式操作 只能 if 嵌套或提前 return 组合子链式处理
解构 if (auto* p = get_if(&opt)) if let Some(v) = opt(模式匹配)
短路传播 ❌ 没有 ? ? 对 Option 同样有效

C++ 的痛点不在”有没有 optional”,而在缺少函数式组合子和模式匹配。看一个实际对比:

1
2
3
4
5
6
7
8
// C++:从环境变量读取端口号,缺省 8080
int get_port() {
const char* env = std::getenv("PORT");
if (!env) return 8080;
int p = std::stoi(env);
if (p <= 0 || p > 65535) return 8080;
return p;
}
1
2
3
4
5
6
7
8
// Rust:同样的逻辑,链条清晰
fn get_port() -> u16 {
std::env::var("PORT")
.ok()
.and_then(|s| s.parse().ok())
.filter(|&p| p > 0 && p <= 65535)
.unwrap_or(8080)
}

C++ 版本需要 3 次提前 return,中间变量 envp 四处散落。Rust 版本从上往下一条链:读取 → 解析 → 验证 → 默认值,每一步做什么一目了然。

6. 总结

  • Option 的本质只是一个 enum,但标准库提供的组合子让它不需要反复写 match
  • 选组合子的方法:先想**”我要干什么”**(转换?串联?默认值?过滤?),再查表
  • and_then 用于串联可能返回 None 的操作,避免嵌套;map 用于纯转换
  • ? 在返回 Option 的函数中自动传播 None,和 Result 的用法完全一致
  • 和 C++ std::optional 的差距不在类型本身,在语言层面的模式匹配 + 函数式组合子 + ? 运算符的组合效应

OptionResult 是 Rust 错误处理的左膀右臂。掌握组合子之后,你会发现代码里 match 越来越少见——不是不用,而是被更精确的工具取代了。