Rust Result
Option 表示一个可能缺失的值,而 Result 表示一个可能失败的操作。和“值不存在”不同,失败通常会附带一个原因。
Result 长什么样
Section titled “Result 长什么样”Result 是标准库里的另一个枚举,这次它的两个变体都带着数据:
enum Result<T, E> { Ok(T), // 成功,装着一个值 Err(E), // 失败,装着一个错误}一个可能失败的函数
Section titled “一个可能失败的函数”你在类型转换那一页见过的 .parse() 就是一个很好的真实例子:把文本转换成数字,如果文本根本不是数字,就会失败:
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}输出:
Ok(42)Err(ParseIntError { kind: InvalidDigit })用 match 处理 Result
Section titled “用 match 处理 Result”fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("解析成功:{number}"), Err(e) => println!("解析失败:{e}"), }}写法和用 match 处理 Option 一样,区别在于 Err 分支会给你一个真正的错误值,你可以检查它、报告它,而不只是“什么都没有”。
自己写一个返回 Result 的函数
Section titled “自己写一个返回 Result 的函数”fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("不能除以零")) } else { Ok(a / b) }}
fn main() { match divide(10.0, 2.0) { Ok(result) => println!("结果:{result}"), Err(e) => println!("错误:{e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("结果:{result}"), Err(e) => println!("错误:{e}"), }}输出:
结果:5错误:不能除以零返回 Result<f64, String> 会强制每个调用者面对“可能失败”这件事:你不可能一不小心把除以零的结果当成普通数字来用。
把好几个可能失败的操作串起来,很容易写出层层嵌套的 match。? 运算符能把它拍平。它的意思是:“如果这是 Err,就立刻从当前函数返回这个错误;否则,把 Ok 里面的值给我。”
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // 解析失败就提前返回 Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("oops"));}输出:
Ok(42)Err(ParseIntError { kind: InvalidDigit })如果没有 ?,这一行就得单独写一个 match,要么取出 number,要么带着错误提前返回。? 用一个字符就做完了这件事。它只能用在本身返回 Result(或 Option)的函数里,因为这样错误才有地方可以返回。
Result 和 panic! 怎么选
Section titled “Result 和 panic! 怎么选”Rust 还有 panic!,它会立刻让程序停下来,下一页会详细讲。一个简单的判断标准:对于调用者有理由想要处理的失败(输入有误、文件不存在),用 Result;panic! 留给那些说明代码本身有 bug 的情况,这时候继续运行比停下来更糟。
当你的函数会以好几种不同的方式失败时,一个简单的 String 错误就不够用了。自定义错误类型那一页会教你定义自己的错误类型,anyhow 与 thiserror 那一页则介绍大多数 Rust 项目用来减少样板代码的 crate。
1. 检查年龄
Section titled “1. 检查年龄”写一个函数 fn parse_age(input: &str) -> Result<u32, String>。如果文本不是数字,或者数字大于 150,就返回带有清楚提示信息的 Err。用 "34"、"abc" 和 "200" 测试它。提示:对 input.parse::<u32>() 做 match,在 Ok 分支里用 if 检查大小。
查看答案
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age} 岁太大了,不太可能是真的")) } else { Ok(age) } } Err(_) => Err(format!("“{input}”不是数字")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("年龄:{age}"), Err(e) => println!("错误:{e}"), } }}输出:
年龄:34错误:“abc”不是数字错误:200 岁太大了,不太可能是真的2. 用 ? 把两个数相加
Section titled “2. 用 ? 把两个数相加”写一个函数 fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError>,解析两个字符串并返回它们的和。用 ?,不要用 match。分别打印 ("10", "20") 和 ("10", "twenty") 的结果。提示:每个 parse 后面跟一个 ?,最后一行用 Ok(...) 把和包起来。
查看答案
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "twenty"));}输出:
Ok(30)Err(ParseIntError { kind: InvalidDigit })Result<T, E>表示一个可能成功(Ok)、也可能失败(Err,带着错误值)的操作。match显式地处理两种情况,和Option的写法一样。?运算符会自动提前返回Err,所以不需要在每个可能失败的调用后面都写一个match。- 对于调用者有理由恢复的错误,优先用
Result,而不是panic!。
小测验
1. Result<T, E> 的两个变体是什么?
2. 在返回 Result 的函数里,? 运算符做了什么?
3. 按照这一页的说法,什么时候应该优先用 Result 而不是 panic!?
4. 在本页的 divide 例子中,divide(10.0, 0.0) 返回什么?
5. 为什么 ? 运算符只能用在本身返回 Result(或 Option)的函数里?
得分: 0 / 5
- 用 Result 处理可恢复的错误 — Rust 程序设计语言:官方教程讲
Result、unwrap、expect和?运算符的章节,用打开文件作为例子。 std::result— 标准库文档(英文):使用Result的概览,包括map_err、unwrap_or、ok等方法。- 结果 Result — 通过例子学 Rust:处理
Result、以及从main返回Result的可运行示例。 - ? 用法 — 通过例子学 Rust:简短地看看
?是怎样取代嵌套match的。