使用 Box
处理错误
通过对错误类型实现 Display
和 From
,我们能够利用上绝大部分标准库错误处理工具。然而,我们遗漏了一个功能:轻松 Box
我们错误类型的能力。
标准库会自动通过 Form
将任意实现了 Error
trait 的类型转换成 trait 对象 Box<Error>
的类型(原文:The std
library automatically converts any type that implements the Error
trait into the trait object Box<Error>
, via From
. )。对于一个库用户,下面可以很容易做到:
fn foo(...) -> Result<T, Box<Error>> { ... }
用户可以使用一系列外部库,其中每个都提供各自错误类型。为了定义一个有效的 Result<T, E>
类型,用户有几个选择:
- 定义一个新的限定在外部库错误类型的包装(wrapper)错误类型(原文:define a new wrapper error type around the libraries error types)
- 将错误类型转换成
String
或者其他合适的选择 - 通过类型擦除(type erasure)将错误类型装包(
Box
)成Box<Error>
将内容“装包”(”Boxing”)是一个常见的选择。缺点是潜在的错误类型只能在运行时知道,且不能静态确定(statically determined)。正如刚才提到的,要做到这点所有要做的事情就是实现 Error
trait:
trait Error: Debug + Display {
fn description(&self) -> &str;
fn cause(&self) -> Option<&Error>;
}
有了这个实现后,我们再来回顾前面学过的最近例子。注意到它所带的错误类型 Box<Error>
也变成有效的了,就像前面用到的 DoubleError
那样(原文:With this implementation, let’s look at our most recent example. Note that it is just as valid with the error type of Box<Error>
as it was before with DoubleError
):
use std::error;
use std::fmt;
use std::num::ParseIntError;
// 将别名更改为 `Box<error::Error>`。
type Result<T> = std::result::Result<T, Box<error::Error>>;
#[derive(Debug)]
enum DoubleError {
EmptyVec,
Parse(ParseIntError),
}
impl From<ParseIntError> for DoubleError {
fn from(err: ParseIntError) -> DoubleError {
DoubleError::Parse(err)
}
}
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"),
DoubleError::Parse(ref e) => e.fmt(f),
}
}
}
impl error::Error for DoubleError {
fn description(&self) -> &str {
match *self {
// 错误的简短说明。不需要和 `Display` 一样。
DoubleError::EmptyVec => "empty vectors not allowed",
// 这已经实现了 `Error`,所以遵循它自己的实现。
DoubleError::Parse(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
// 没有潜在的差错,所以返回 `None`。
DoubleError::EmptyVec => None,
// 差错为底层实现的错误类型。被隐式地转换成 trait 对象 `&error::Error`。
// 这会正常工作,因为底层的类型已经实现了 `Error` trait。
DoubleError::Parse(ref e) => Some(e),
}
}
}
fn double_first(vec: Vec<&str>) -> Result<i32> {
let first = try!(vec.first().ok_or(DoubleError::EmptyVec));
let parsed = try!(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!["93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];
print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}