Rust Result
Result 是 Option 类型的更丰富的版本,描述的是可能的错误而不是可能的不存在。
也就是说,Result<T,E> 可以有两个结果的其中一个:
- Ok
:找到 T 元素 - Err
:找到 E 元素,E 即表示错误的类型。
按照约定,预期结果是 “Ok”,而意外结果是 “Err”。
Result 有很多类似 Option 的方法。例如 unwrap(),它要么举出元素 T,要么就 panic。 对于事件的处理,Result 和 Option 有很多相同的组合算子。
在使用 Rust 时,你可能会遇到返回 Result 类型的方法,例如 parse() 方法。它并不是总能把字符串解析成指定的类型,所以 parse() 返回一个 Result 表示可能的失败。
我们来看看当 parse() 字符串成功和失败时会发生什么:
fn multiply(first_number_str: &str, second_number_str: &str) -> i32 {
// 我们试着用 `unwrap()` 把数字放出来。它会咬我们一口吗?
let first_number = first_number_str.parse::<i32>().unwrap();
let second_number = second_number_str.parse::<i32>().unwrap();
first_number * second_number
}
fn main() {
let twenty = multiply("10", "2");
println!("double is {}", twenty);
let tt = multiply("t", "2");
println!("double is {}", tt);
}
在失败的情况下,parse() 产生一个错误,留给 unwrap() 来解包并产生 panic。另外,panic 会退出我们的程序,并提供一个让人很不爽的错误消息。
为了改善错误消息的质量,我们应该更具体地了解返回类型并考虑显式地处理错误。
Result 的 map
上一节的 multiply 函数的 panic 设计不是健壮的(robust)。一般地,我们希望把错误返回给调用者,这样它可以决定回应错误的正确方式。
首先,我们需要了解需要处理的错误类型是什么。为了确定 Err 的类型,我们可以用 parse() 来试验。Rust 已经为 i32 类型使用 FromStr trait 实现了 parse()。结果表明,这里的 Err 类型被指定为 ParseIntError。
在下面的例子中,使用简单的 match 语句导致了更加繁琐的代码。
use std::num::ParseIntError;
// 修改了上一节中的返回类型,现在使用模式匹配而不是 `unwrap()`。
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
match first_number_str.parse::<i32>() {
Ok(first_number) => {
match second_number_str.parse::<i32>() {
Ok(second_number) => {
Ok(first_number * second_number)
},
Err(e) => Err(e),
}
},
Err(e) => Err(e),
}
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
// 这种情形下仍然会给出正确的答案。
let twenty = multiply("10", "2");
print(twenty);
// 这种情况下就会提供一条更有用的错误信息。
let tt = multiply("t", "2");
print(tt);
}
幸运的是,Option 的 map、and_then、以及很多其他组合算子也为 Result 实现了。官方文档的 Result 一节包含完整的方法列表。
use std::num::ParseIntError;
// 就像 `Option` 那样,我们可以使用 `map()` 之类的组合算子。
// 除去写法外,这个函数与上面那个完全一致,它的作用是:
// 如果值是合法的,计算其乘积,否则返回错误。
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
first_number_str.parse::<i32>().and_then(|first_number| {
second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
})
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
// 这种情况下仍然会给出正确的答案。
let twenty = multiply("10", "2");
print(twenty);
// 这种情况下就会提供一条更有用的错误信息。
let tt = multiply("t", "2");
print(tt);
}
给 Result 取别名
当我们要重用某个 Result 类型时,该怎么办呢?回忆一下,Rust 允许我们创建别名。若某个 Result 有可能被重用,我们可以方便地给它取一个别名。
在模块的层面上创建别名特别有帮助。同一模块中的错误常常会有相同的 Err 类型,所以单个别名就能简便地定义所有相关的 Result。这太有用了,以至于标准库也提供了一个别名: io::Result!
下面给出一个简短的示例来展示语法:
use std::num::ParseIntError;
// 为带有错误类型 `ParseIntError` 的 `Result` 定义一个泛型别名。
type AliasedResult<T> = Result<T, ParseIntError>;
// 使用上面定义过的别名来表示上一节中的 `Result<i32,ParseIntError>` 类型。
fn multiply(first_number_str: &str, second_number_str: &str) -> AliasedResult<i32> {
first_number_str.parse::<i32>().and_then(|first_number| {
second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
})
}
// 在这里使用别名又让我们节省了一些代码量。
fn print(result: AliasedResult<i32>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
print(multiply("10", "2"));
print(multiply("t", "2"));
}
提前返回
在上一个例子中,我们显式地使用组合算子处理了错误。另一种处理错误的方式是使用 match 语句和提前返回(early return)的结合。
这也就是说,如果发生错误,我们可以停止函数的执行然后返回错误。对有些人来说,这样的代码更好写,更易读。这次我们使用提前返回改写之前的例子:
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
let first_number = match first_number_str.parse::<i32>() {
Ok(first_number) => first_number,
Err(e) => return Err(e),
};
let second_number = match second_number_str.parse::<i32>() {
Ok(second_number) => second_number,
Err(e) => return Err(e),
};
Ok(first_number * second_number)
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
print(multiply("10", "2"));
print(multiply("t", "2"));
}
到此为止,我们已经学会了如何使用组合算子和提前返回显式地处理错误。我们一般是想要避免 panic 的,但显式地处理所有错误确实显得过于繁琐。
在下一部分,我们将看到,当只是需要 unwrap 并且不产生 panic 时,可以使用 ? 来达到同样的效果。
引入?
有时我们只是想 unwrap 且避免产生 panic。到现在为止,对 unwrap 的错误处理都在强迫我们一层层地嵌套,然而我们只是想把里面的变量拿出来。? 正是为这种情况准备的。
当找到一个 Err 时,可以采取两种行动:
- panic!,不过我们已经决定要尽可能避免 panic 了。
- 返回它,因为 Err 就意味着它已经不能被处理了。
? 几乎就等于一个会返回 Err 而不是 panic 的 unwrap。我们来看看怎样简化之前使用组合算子的例子:
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
let first_number = first_number_str.parse::<i32>()?;
let second_number = second_number_str.parse::<i32>()?;
Ok(first_number * second_number)
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
print(multiply("10", "2"));
print(multiply("t", "2"));
}
历史知识:try! 宏
在 ? 出现以前,相同的功能是使用 try! 宏完成的。现在我们推荐使用 ? 运算符,但是在老代码中仍然会看到 try!。如果使用 try! 的话,上一个例子中的 multiply 函数看起来会像是这样:
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
let first_number = try!(first_number_str.parse::<i32>());
let second_number = try!(second_number_str.parse::<i32>());
Ok(first_number * second_number)
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
print(multiply("10", "2"));
print(multiply("t", "2"));
}