外观
雷区 8:Option 与 Result 混着用,方法链不会接
症状
写真实项目,Option 和 Result 经常前后脚出现:
- "找配置里的年龄"(没有 →
None)→ "把年龄文字转成数字"(可能失败 →Err) - "批量解析一堆输入"(其中可能有失败)→ 想一把全收
- "把
Vec<Result<T, E>>变成Result<Vec<T>, E>"
这时候最容易写出"两套 match 叠着"的代码:
rust
fn parse_age(input: Option<&str>) -> Option<u32> {
match input {
Some(text) => match text.parse() {
Ok(number) => Some(number),
Err(_) => None,
},
None => None,
}
}两层 match 套着,代码还没开始业务,先被"拆信封"淹没了。 而且 Option 和 Result 混在一起时,更不知道该怎么接。
诊断
Option 和 Result 是同构的:都是"信封,要么有值要么没有"。区别只是"没有"的时候,Result 多带一个原因(Err)。既然长得像,就该有一套统一的方法链——Rust 标准库早就配好了,只是很多人没用熟。
高频转换器四兄弟:
| 方法 | 从 | 到 | 干什么 |
|---|---|---|---|
.ok_or(原因) | Option | Result | "没有" → "失败,带原因" |
.ok() | Result | Option | "失败" → "没有"(原因扔掉) |
.transpose() | Option<Result<T,E>> | Result<Option<T>,E> | 交换两层 |
collect | Vec<Result<T,E>> | Result<Vec<T>,E> | 全收或全退 |
解药一:Option → Result,用 ok_or
rust
fn parse_age(input: Option<&str>) -> Result<u32, String> {
let text = input.ok_or("没有输入年龄")?;
text.parse().map_err(|_| "年龄不是数字".to_string())
}
fn main() {
println!("{:?}", parse_age(Some("10"))); // Ok(10)
println!("{:?}", parse_age(None)); // Err("没有输入年龄")
println!("{:?}", parse_age(Some("abc"))); // Err("年龄不是数字")
}ok_or 把"有没有"升级成"成不成功"——None 变成 Err(原因),从此一条 ? 链走到底。
解药二:两层一起收,用 transpose
rust
fn find_age(input: Option<&str>) -> Result<Option<u32>, String> {
let parsed = input.map(|text| text.parse::<u32>());
// parsed: Option<Result<u32, _>>
parsed.transpose().map_err(|_| "年龄不是数字".to_string())
}
fn main() {
println!("{:?}", find_age(Some("10"))); // Ok(Some(10))
println!("{:?}", find_age(None)); // Ok(None)——没输入不算错
println!("{:?}", find_age(Some("abc"))); // Err("年龄不是数字")
}transpose 把 Option<Result<T,E>> 翻成 Result<Option<T>,E>——"没有输入"是合法的 Ok(None),输错了才是 Err。
解药三:一批全收,用 collect
rust
fn main() {
let results: Vec<Result<u32, String>> = vec![Ok(1), Ok(2), Ok(3)];
let all: Result<Vec<u32>, String> = results.into_iter().collect();
println!("全部成功:{:?}", all); // Ok([1, 2, 3])
let results: Vec<Result<u32, String>> = vec![Ok(1), Err("坏了".to_string()), Ok(3)];
let all: Result<Vec<u32>, String> = results.into_iter().collect();
println!("有失败:{:?}", all); // Err("坏了")
}collect 遇上 Result 有魔法:要么全部成功收成一串,要么第一个失败直接短路——一个 collect,代替"循环 + 逐项 match + 提前 return"。
预防
- 看到"两层 match 拆信封",先想转换器:
ok_or/ok/transpose/collect/and_then(前一步成功才继续),十有八九能压成一行 and_then是"先 Option 后 Result"的桥梁:opt.and_then(|v| v.parse().ok())——Option链里插Result操作- 方法链的读法:从左往右"先是什么,变成什么"——
input.ok_or("没输入")?.parse().map_err(...)读作"先确保有输入,再解析,解析失败给原因" - 需要"原因"用 Result,只需要"有没有"用 Option:方法链里按需转换,别让两套信封卡住业务