首页
学习
活动
专区
工具
TVP
发布

failure-错误处理库

前言

错误处理在生产级别的代码中一直都是一个重点。在原型阶段,愉快地使用unwrap可以确保思路和精力被集中用在业务逻辑开发上。不过对于最终要上线的代码,优雅的处理错误却是至关重要的。原生Rust错误处理的工具有std::error::Error(一般我们会看到Box的形式),?操作符以及enum供我们自定义错误类型。这本身就可以作为一个专题来讨论。而今天我们就来简单介绍一下failure库以及其背后的错误处理哲学。

简介

failure是rust-lang-nursery下的一个库,可以说是根正苗红的rust库了。其目标是取代基于std::eror::Error的错误处理。

failure有两个核心组件

Fail: 定制错误类型用的trait

Error: 只要实现了Fail,就能转化为该结构体

Fail trait

Fail trait被用来取代std::error::Error。它提供了backtrace和cause方法去获取错误的信息。也支持将错误包装在上下文(context)中。所有新的错误类型都应该实现该trait。对于一个实现了Fail的fail,我们可以逐层打印出错误链条

let mut fail: &Fail = err;

while let Some(cause) = fail.cause() {

println!("{}", cause);

fail = cause;

}

也可以打印出backtrace错误回溯

if let Some(bt) = err.cause().and_then(|cause| cause.backtrace()) {

println!("{}", bt)

}

也可以引入ResultExt从而给错误加入上下文(context)以分辨想通的错误。比如为了区分io错误,我们可以:

use failure::ResultExt;

let mut file = File::open(cargo_toml_path).context("Missing Cargo.toml")?;

file.read_to_end(&buffer).context("Could not read Cargo.toml")?;

自动推导Fail

一般来说Fail可以方便的被derive出来,只需要自己去实现Display即可

extern crate failure;

#[macro_use] extern crate failure_derive;

use std::fmt;

#[derive(Fail, Debug)]

struct MyError;

impl fmt::Display for MyError {

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

write!(f, "An error occurred.")

}

}

Error

而只要实现了Fail trait的类型,都可以通过?被转化成Error。比如下边的例子中,IO错误和JSON错误都被转化成了Result中的Error

#[derive(Deserialize)]

struct Object {

...

}

impl Object {

fn from_file(path: &Path) -> Result {

let mut string = String::new();

File::open(path)?.read_to_string(&mut string)?;

let object = json::from_str(&string)?;

Ok(object)

}

}

几个模式

简单说来,有如下几个模式可供选择:

使用字符串:比较适合原型计算。

定义自己的Fail实现:定义一个自己的错误类型,比较适合需要对错误有较多控制的库。

使用Error:使用Error统一处理多处不同的返回错误。比较适合不太需要详细检查error内容的应用或库。

使用Error和ErrorKind对:使用Error类型和ErrorKind枚举创建一个健壮的错误类型。比较适合作为大型库的公共API。

使用字符串

这是一个比较简便的方法,推荐在原型阶段使用。format_err宏可以很容易的生成一个Error。

fn check_range(x: usize, range: Range) -> Result {

if x < range.start {

return Err(format_err!("{} is below {}", x, range.start));

}

if x >= range.end {

return Err(format_err!("{} is above {}", x, range.end));

}

Ok(x)

}

除了原型阶段之外,当错误非常罕见以及对错误的处理只能局限于打印的情况下,可以使用这个模式。很明显,在这个模式下能对返回的错误做出的操作非常有限。如果需要知道错误的具体类型/内容,还需要做字符串匹配。

定义自己的Fail实现

接下来就是自定义实现了Fail trait的错误,这可以通过derive宏很容易的实现。这样做有三大好处:

可以遍历所有的错误

可以完全控制错误的表达

调用者可以直接析构出错误 可以看到在例子中,我们可以给错误增加想要的信息。

#[derive(Fail, Debug)]

#[fail(display = "Input was invalid UTF-8 at index {}", index)]

pub struct Utf8Error {

index: usize,

}

在对待从依赖以外的部分返回的错误是,可以使用这一策略。

使用Error

当一个函数中会返回多种错误时可以使用这一模式,其具有以下特点:

开始时不需要自定义类型

实现了Fail trait的类型只要使用?操作符就可以变为Error类型

当你引入新的依赖和新的错误类型时,你可以直接抛出它们 要使用该模式,只要把返回值设定为Result

use std::io;

use std::io::BufRead;

use failure::Error;

use failure::err_msg;

fn my_function() -> Result {

let stdin = io::stdin();

for line in stdin.lock().lines() {

let line = line?;

if line.chars().all(|c| c.is_whitespace()) {

break

}

if !line.starts_with("$") {

return Err(format_err!("Input did not begin with `$`"));

}

println!("{}", &line[1..]);

}

Ok(())

}

一般来说当你不需要析构返回的错误时可以采用这一模式。包括:

原型设计时

绝大多数时间你只会打印错误的时候

不需要得到关于错误的更多信息的时候

使用Error和ErrorKind对

这是最健壮的处理错误的方式,当然也需要更多的维护成本。它相当于结合了定义自己的Fail实现和使用Error的优点:

和Error一样向前兼容新的错误类型,而依赖中的错误已经可以被转化成该错误类型

像自定义Fail一样可以给错误提供额外的信息,而用户很容易得到这些信息 你需要创建Error类型以及ErrorKind枚举:

#[derive(Debug)]

struct MyError {

inner: Context,

}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]

enum MyErrorKind {

#[fail(display = "A contextual error message.")]

OneVariant,

// ...

}

但是你必须自己实现Fail,以及提供一些转换函数

impl Fail for MyError {

fn cause(&self) -> Option {

self.inner.cause()

}

fn backtrace(&self) -> Option {

self.inner.backtrace()

}

}

impl Display for MyError {

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

Display::fmt(&self.inner, f)

}

}

impl MyError {

pub fn kind(&self) -> MyErrorKind {

*self.inner.get_context()

}

}

impl From for MyError {

fn from(kind: MyErrorKind) -> MyError {

MyError { inner: Context::new(kind) }

}

}

impl From for MyError {

fn from(inner: Context) -> MyError {

MyError { inner: inner }

}

}

这样你就可以使用把ErrorKind注入到使用的函数中

perform_some_io().context(ErrorKind::NetworkFailure)?;

当然你也可以直接抛出错误

Err(ErrorKind::DomainSpecificError)?

该模式最适合处于中间层并且有许多依赖的生产环境代码。

小结

错误的坑真是深似水,这里也只是对failure这一著名的错误处理库做了初步的介绍,祝大家一起在错误中成长~

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200327A0PY6000?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券