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 opt { Some(v) => println!("{}", v), None => println!("empty"), }
if let Some(v) = opt { println!("{}", v); }
|
这两种方式在上一篇文章中已经介绍。它们适用于需要分别处理两种情况的场景。但很多时候你只想做一件事——取值、转换、或给默认值——这时组合子比 match 更简洁。
unwrap / expect
直接取走 Some 内部的值,None 时 panic:
1 2
| let v = opt.unwrap(); let v = opt.expect("必须存在");
|
使用原则: 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");
let len = opt.map(|s| s.len());
let len = opt.map_or(0, |s| s.len());
let len = opt.map_or_else(|| 0, |s| s.len());
|
实际场景——解析环境变量:
1 2 3 4 5
| let port = std::env::var("PORT") .ok() .map(|s| s.parse::<u16>()) .and_then(|r| r.ok()) .map_or(8080, |p| p);
|
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> { }
let result = get_from_cache("user:42").map(|data| get_from_db(&data.next_key));
let result = get_from_cache("user:42").and_then(|data| get_from_db(&data.next_key));
|
多级串联:
1 2 3 4
| let config = std::env::var("CONFIG_FILE") .ok() .and_then(|path| std::fs::read_to_string(path).ok()) .and_then(|content| parse_config(&content));
|
链条中任一步返回 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"); let val = opt.unwrap_or_else(|| expensive_fallback());
|
用 unwrap_or_else 而不是 unwrap_or 的场景:
1 2 3 4 5
| let config = opt.unwrap_or(default_config());
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); let odd = opt.filter(|&n| n % 2 != 0);
|
实际场景——只处理”有意义”的值:
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();
let res: Result<i32, &str> = Err("fail"); let opt = res.ok();
|
1 2
| let res: Result<i32, &str> = Err("fail"); let opt = res.err();
|
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");
let opt: Option<i32> = None; let res: Result<i32, &str> = opt.ok_or("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()?; 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() .filter(|v| !v.is_empty()) .ok_or_else(|| format!("{} not set", key).into()) }
|
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();
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
| 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
| 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,中间变量 env、p 四处散落。Rust 版本从上往下一条链:读取 → 解析 → 验证 → 默认值,每一步做什么一目了然。
6. 总结
Option 的本质只是一个 enum,但标准库提供的组合子让它不需要反复写 match
- 选组合子的方法:先想**”我要干什么”**(转换?串联?默认值?过滤?),再查表
and_then 用于串联可能返回 None 的操作,避免嵌套;map 用于纯转换
? 在返回 Option 的函数中自动传播 None,和 Result 的用法完全一致
- 和 C++
std::optional 的差距不在类型本身,在语言层面的模式匹配 + 函数式组合子 + ? 运算符的组合效应
Option 和 Result 是 Rust 错误处理的左膀右臂。掌握组合子之后,你会发现代码里 match 越来越少见——不是不用,而是被更精确的工具取代了。