跳到主要内容

Result

Result 是 Option 类型的更丰富版本,它描述可能的错误而不是可能的缺失。

也就是说,Result<T, E> 可能有以下两种结果之一:

  • Ok(T) :找到元素 T
  • Err(E) :元素 E 发现错误

按照惯例,预期结果是 Ok 而意外结果是 Err 。

与 Option 一样, Result 有许多与之关联的方法。例如, unwrap() 生成元素 T 或 panic 。对于案例处理, Result 和 Option 之间有许多重叠的组合器。

在使用 Rust 时,您可能会遇到返回 Result 类型的方法,例如 parse() 方法。可能并不总是能够将字符串解析为其他类型,因此 parse() 返回 Result 指示可能失败。

让我们看看当我们成功和失败 parse() 一个字符串时会发生什么:

fn multiply(first_number_str: &str, second_number_str: &str) -> i32 {
// Let's try using `unwrap()` to get the number out. Will it bite us?
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 退出我们的程序并提供令人不快的错误消息。

为了提高错误消息的质量,我们应该更具体地了解返回类型并考虑显式处理错误。

在 main 中使用 Result

如果明确指定, Result 类型也可以是 main 函数的返回类型。通常, main 函数的形式如下:

fn main() {
println!("Hello World!");
}

但是 main 也可以有 Result 返回类型。如果 main 函数中发生错误,它将返回错误代码并打印错误的调试表示(使用 Debug 特征)。以下示例显示了此类场景,并涉及下一节中涵盖的各个方面。

use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
let number_str = "10";
let number = match number_str.parse::<i32>() {
Ok(number) => number,
Err(e) => return Err(e),
};
println!("{}", number);
Ok(())
}

map for Result

上一个示例的 multiply 中的恐慌并不利于代码的健壮。通常,我们希望将错误返回给调用者,以便调用者可以决定响应错误的正确方法。

我们首先需要知道我们正在处理什么类型的错误类型。为了确定 Err 类型,我们查看 parse() ,它是通过 i32 的 FromStr 特征实现的。因此, Err 类型被指定为 ParseIntError 。

在下面的示例中,简单的 match 语句导致代码总体上更加麻烦。

use std::num::ParseIntError;

// With the return type rewritten, we use pattern matching without `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() {
// This still presents a reasonable answer.
let twenty = multiply("10", "2");
print(twenty);

// The following now provides a much more helpful error message.
let tt = multiply("t", "2");
print(tt);
}

幸运的是, Option 的 map 、 and_then 和许多其他组合器也为 Result 实现。 Result 包含完整的列表。

use std::num::ParseIntError;

// As with `Option`, we can use combinators such as `map()`.
// This function is otherwise identical to the one above and reads:
// Multiply if both values can be parsed from str, otherwise pass on the error.
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() {
// This still presents a reasonable answer.
let twenty = multiply("10", "2");
print(twenty);

// The following now provides a much more helpful error message.
let tt = multiply("t", "2");
print(tt);
}

Result 的别名

当我们想要多次重复使用特定的 Result 类型时该怎么办?回想一下,Rust 允许我们创建别名。方便的是,我们可以为所讨论的特定 Result 定义一个。

在模块级别,创建别名特别有用。在特定模块中发现的错误通常具有相同的 Err 类型,因此单个别名可以简洁地定义所有关联的 Results 。这非常有用, std 库甚至提供了一个: io::Result !

这是一个展示语法的简单示例:

use std::num::ParseIntError;

// Define a generic alias for a `Result` with the error type `ParseIntError`.
type AliasedResult<T> = Result<T, ParseIntError>;

// Use the above alias to refer to our specific `Result` type.
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)
})
}

// Here, the alias again allows us to save some space.
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 语句和早期返回的组合。

也就是说,我们可以简单地停止执行该函数并在发生错误时返回错误。对于某些人来说,这种形式的代码更易于阅读和编写。考虑上一个示例的这个版本,使用早期返回重写:

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"));
}

至此,我们已经学会了使用组合器和早期返回来显式处理错误。虽然我们通常希望避免恐慌,但显式处理所有错误是很麻烦的。

在下一节中,我们将在只需要 unwrap 而不会引入 panic 的情况下引入 ? 。

引入 ?

有时我们只想要 unwrap 的简单性,而不需要 panic 。到目前为止, unwrap 迫使我们嵌套得越来越深,而我们真正想要的是取出变量。这正是 ? 的目的。

找到 Err 后,需要采取两个有效操作:

panic! 我们已经决定尽可能避免 return 因为 Err 意味着它无法被处理 ? 几乎与 1 完全等同于 unwrap ,其中 return 而不是 Err 国王 s。让我们看看如何简化前面使用组合器的示例:

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 函数将如下所示:

// To compile and run this example without errors, while using Cargo, change the value 
// of the `edition` field, in the `[package]` section of the `Cargo.toml` file, to "2015".

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"));
}

Result 迭代

迭代 Result 秒 Iter::map 操作可能会失败,例如:

fn main() {
let strings = vec!["tofu", "93", "18"];
let numbers: Vec<_> = strings
.into_iter()
.map(|s| s.parse::<i32>())
.collect();
println!("Results: {:?}", numbers);
}

让我们逐步了解处理此问题的策略。

使用 filter_map() 忽略失败的项目

filter_map 调用函数并过滤掉 None 的结果。

fn main() {
let strings = vec!["tofu", "93", "18"];
let numbers: Vec<_> = strings
.into_iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
println!("Results: {:?}", numbers);
}

收集 map_err() 和 filter_map() 失败的项目

map_err 调用一个带有错误的函数,因此通过将其添加到之前的 filter_map 解决方案中,我们可以在迭代时将它们保存到一边。

fn main() {
let strings = vec!["42", "tofu", "93", "999", "18"];
let mut errors = vec![];
let numbers: Vec<_> = strings
.into_iter()
.map(|s| s.parse::<u8>())
.filter_map(|r| r.map_err(|e| errors.push(e)).ok())
.collect();
println!("Numbers: {:?}", numbers);
println!("Errors: {:?}", errors);
}

使用 collect() 使整个操作失败

Result 实现 FromIterator ,以便结果向量 ( Vec<Result<T, E>> ) 可以转换为带有向量的结果 ( Result<Vec<T>, E> )。一旦找到 Result::Err ,迭代就会终止。

fn main() {
let strings = vec!["tofu", "93", "18"];
let numbers: Result<Vec<_>, _> = strings
.into_iter()
.map(|s| s.parse::<i32>())
.collect();
println!("Results: {:?}", numbers);
}

同样的技术也可以用于 Option 。

使用 partition() 收集所有有效值和失败值

fn main() {
let strings = vec!["tofu", "93", "18"];
let (numbers, errors): (Vec<_>, Vec<_>) = strings
.into_iter()
.map(|s| s.parse::<i32>())
.partition(Result::is_ok);
println!("Numbers: {:?}", numbers);
println!("Errors: {:?}", errors);
}

当您查看结果时,您会注意到所有内容仍然包含在 Result 中。为此还需要更多的样板文件。

fn main() {
let strings = vec!["tofu", "93", "18"];
let (numbers, errors): (Vec<_>, Vec<_>) = strings
.into_iter()
.map(|s| s.parse::<i32>())
.partition(Result::is_ok);
let numbers: Vec<_> = numbers.into_iter().map(Result::unwrap).collect();
let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect();
println!("Numbers: {:?}", numbers);
println!("Errors: {:?}", errors);
}
Loading Comments...