跳到主要内容

多种错误类型

前面的例子一直都很方便; Result 与其他 Result 交互, Option 与其他 Option 交互。

有时 Option 需要与 Result 交互,或者 Result<T, Error1> 需要与 Result<T, Error2> 交互。在这些情况下,我们希望以一种使它们可组合且易于交互的方式来管理不同的错误类型。

在以下代码中, unwrap 的两个实例生成不同的错误类型。 Vec::first 返回 Option ,而 parse::<i32> 返回 Result<i32, ParseIntError>

fn double_first(vec: Vec<&str>) -> i32 {
let first = vec.first().unwrap(); // Generate error 1
2 * first.parse::<i32>().unwrap() // Generate error 2
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

println!("The first doubled is {}", double_first(numbers));

println!("The first doubled is {}", double_first(empty));
// Error 1: the input vector is empty

println!("The first doubled is {}", double_first(strings));
// Error 2: the element doesn't parse to a number
}

将 Result 从 Option 中取出

处理混合错误类型的最基本方法是将它们相互嵌入。

use std::num::ParseIntError;

fn double_first(vec: Vec<&str>) -> Option<Result<i32, ParseIntError>> {
vec.first().map(|first| {
first.parse::<i32>().map(|n| 2 * n)
})
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

println!("The first doubled is {:?}", double_first(numbers));

println!("The first doubled is {:?}", double_first(empty));
// Error 1: the input vector is empty

println!("The first doubled is {:?}", double_first(strings));
// Error 2: the element doesn't parse to a number
}

有时我们希望停止处理错误(例如 ? ),但当 Option 为 None 时继续处理。几个组合器可以派上用场来交换 Result 和 Option 。

use std::num::ParseIntError;

fn double_first(vec: Vec<&str>) -> Result<Option<i32>, ParseIntError> {
let opt = vec.first().map(|first| {
first.parse::<i32>().map(|n| 2 * n)
});

opt.map_or(Ok(None), |r| r.map(Some))
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

println!("The first doubled is {:?}", double_first(numbers));
println!("The first doubled is {:?}", double_first(empty));
println!("The first doubled is {:?}", double_first(strings));
}

定义错误类型

有时,它会简化代码,用单一类型的错误掩盖所有不同的错误。我们将用自定义错误来展示这一点。

Rust 允许我们定义自己的错误类型。一般来说,“好的”错误类型:

  1. 代表相同类型的不同错误
  2. 向用户呈现漂亮的错误消息
  3. 易于与其他类型进行比较
    1. 好: Err(EmptyVec)
    2. 坏: Err("Please use a vector with at least one element".to_owned())
  4. 可以保存有关错误的信息
    1. 好: Err(BadChar(c, position))
    2. 坏: Err("+ cannot be used here".to_owned())
  5. 与其他错误组合良好
use std::fmt;

type Result<T> = std::result::Result<T, DoubleError>;

// Define our error types. These may be customized for our error handling cases.
// Now we will be able to write our own errors, defer to an underlying error
// implementation, or do something in between.
#[derive(Debug, Clone)]
struct DoubleError;

// Generation of an error is completely separate from how it is displayed.
// There's no need to be concerned about cluttering complex logic with the display style.
//
// Note that we don't store any extra info about the errors. This means we can't state
// which string failed to parse without modifying our types to carry that information.
impl fmt::Display for DoubleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
// Change the error to our new type.
.ok_or(DoubleError)
.and_then(|s| {
s.parse::<i32>()
// Update to the new error type here also.
.map_err(|_| DoubleError)
.map(|i| 2 * i)
})
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

Box 错误

在保留原始错误的同时编写简单代码的一种方法是 Box 它们。缺点是底层错误类型仅在运行时已知,而不是静态确定的。

stdlib 通过 Box 实现从任何实现 Error 特征的类型到特征对象 Box<Error> 的转换,通过 From

use std::error;
use std::fmt;

// Change the alias to use `Box<dyn error::Error>`.
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug, Clone)]
struct EmptyVec;

impl fmt::Display for EmptyVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}

impl error::Error for EmptyVec {}

fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
.ok_or_else(|| EmptyVec.into()) // Converts to Box
.and_then(|s| {
s.parse::<i32>()
.map_err(|e| e.into()) // Converts to Box
.map(|i| 2 * i)
})
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

? 的其他用途

请注意,在前面的示例中,我们对调用 parse 的立即反应是将 map 错误从库错误转换为盒装错误:

.and_then(|s| s.parse::<i32>())
.map_err(|e| e.into())

由于这是一个简单且常用的操作,如果可以省略的话会很方便。唉,因为 and_then 不够灵活,所以不能。但是,我们可以改用 ? 。

? 之前被解释为 unwrap 或 return Err(err) 。这只是大部分正确的。它实际上意味着 unwrap 或 return Err(From::from(err)) 。由于 From::from 是不同类型之间的转换实用程序,这意味着如果您 ? 将错误转换为返回类型,它将自动转换。

在这里,我们使用 ? 重写前面的示例。因此,当为我们的错误类型实现 From::from 时, map_err 将消失:

use std::error;
use std::fmt;

// Change the alias to use `Box<dyn error::Error>`.
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug)]
struct EmptyVec;

impl fmt::Display for EmptyVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}

impl error::Error for EmptyVec {}

// The same structure as before but rather than chain all `Results`
// and `Options` along, we `?` to get the inner value out immediately.
fn double_first(vec: Vec<&str>) -> Result<i32> {
let first = vec.first().ok_or(EmptyVec)?;
let parsed = first.parse::<i32>()?;
Ok(2 * parsed)
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

现在实际上已经相当干净了。与原来的 panic 相比,它与将 unwrap 调用替换为 ? 非常相似,只是返回类型为 Result 。因此,它们必须在顶层被解构。

换行错误

装箱错误的另一种方法是将它们包装在您自己的错误类型中。

use std::error;
use std::error::Error;
use std::num::ParseIntError;
use std::fmt;

type Result<T> = std::result::Result<T, DoubleError>;

#[derive(Debug)]
enum DoubleError {
EmptyVec,
// We will defer to the parse error implementation for their error.
// Supplying extra info requires adding more data to the type.
Parse(ParseIntError),
}

impl fmt::Display for DoubleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
DoubleError::EmptyVec =>
write!(f, "please use a vector with at least one element"),
// The wrapped error contains additional information and is available
// via the source() method.
DoubleError::Parse(..) =>
write!(f, "the provided string could not be parsed as int"),
}
}
}

impl error::Error for DoubleError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
DoubleError::EmptyVec => None,
// The cause is the underlying implementation error type. Is implicitly
// cast to the trait object `&error::Error`. This works because the
// underlying type already implements the `Error` trait.
DoubleError::Parse(ref e) => Some(e),
}
}
}

// Implement the conversion from `ParseIntError` to `DoubleError`.
// This will be automatically called by `?` if a `ParseIntError`
// needs to be converted into a `DoubleError`.
impl From<ParseIntError> for DoubleError {
fn from(err: ParseIntError) -> DoubleError {
DoubleError::Parse(err)
}
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
let first = vec.first().ok_or(DoubleError::EmptyVec)?;
// Here we implicitly use the `ParseIntError` implementation of `From` (which
// we defined above) in order to create a `DoubleError`.
let parsed = first.parse::<i32>()?;

Ok(2 * parsed)
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => {
println!("Error: {}", e);
if let Some(source) = e.source() {
println!(" Caused by: {}", source);
}
},
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

这增加了一些用于处理错误的样板,并且可能并非在所有应用程序中都需要。有一些库可以为您处理样板文件。

Loading Comments...