错误处理
参考Rust Book,错误处理可以分成两种,可恢复的错误和不可恢复的错误。可恢复的错误对应Result
,不可恢复的错误对应 panic
。
Result
Result 是可恢复错误处理的基础,常见的对 Result 的简单处理有以下方法:
result.unwrap(); // 适合相信一定不会失败,或者写demo
result.expect("xxx"); // 适合失败了也没有办法处理,报个错panic
// 适合失败了也可以处理
if let Ok(x) = result {
todo!();
} else {
todo!();
}
result.unwrap_or(); // 失败了就给个默认值
根据编码规范的建议,应该在会出现错误的函数,以及会 Panic 的函数添加什么时候会出现错误的说明。
#![warn(clippy::missing_errors_doc)]
#![warn(clippy::missing_panics_doc)]
/// # Errors
///
/// Will return `Err` if ...
///
/// # Panics
///
/// Will panic if ...
pub fn read(filename: String) -> io::Result<String> {
unimplemented!();
}
? 和 Try
?
是Rust提供的语法糖,让我们免于编写错误处理函数,而是直接将错误返回,让调用者负责处理错误。
需要注意的是,?
表达式返回的错误类型需要和函数参数匹配,不是所有的返回 Result 的函数都能用 ?
一把梭。虽然你可以用 Result<T,Box<dyn Error>>
,但这样并不好。可以用下面介绍的函数式做一些处理。
如果为了省事,可以用 Anyhow 这个库。你就可以随便用 ?
了。更科学的做法是为自己程序可能发生的错误定义好错误类型,thiserror 库可以帮忙。
下面探究一下 ?
的内部实现,首先需要了解 trait Try
pub trait Try: FromResidual<Self::Residual> {
type Output;
type Residual;
fn from_output(output: Self::Output) -> Self;
fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
}
pub enum ControlFlow<B, C = ()> {
Continue(C),
Break(B),
}
当我们在写 x?
的时候,其实我们的意思是。
// x: X
match x.branch() {
ControlFlow::Continue(a) => a,
ControlFlow::Break(a) => return X::from_output(a)
}
不难知道 Result
应该实现了 Try
Trait. 下面是实现代码。
// 和标准库有出入https://doc.rust-lang.org/src/core/result.rs.html#1893-1900
impl <T,E> ops::Try for Result<T,E> {
type Output = T;
type Residual = Result<convert::Infallible, E>;
fn from_output(output: Self::Output) -> Self {
Ok(output)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Ok(v) => ControlFlow::Continue(v),
Err(e) => ControlFlow::Break(Err(e))
}
}
}
函数式处理
下面是一系列“语法糖”,其实都可以用 match 做进一步的处理。比如有下面四个
方法 | 原型 | 说明 |
---|---|---|
and | pub fn and<U>(self, res: Result<U, E>) -> Result<U, E> |
Returns res if the result is Ok , otherwise returns the Err value of self . |
and_then | pub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>, |
Calls op if the result is Ok , otherwise returns the Err value of self . |
or | pub fn or<F>(self, res: Result<T, F>) -> Result<T, F> |
Returns res if the result is Err , otherwise returns the Ok value of self . |
or_else | pub fn or_else<F, O>(self, op: O) -> Result<T, F> where O: FnOnce(E) -> Result<T, F>, |
Calls op if the result is Err , otherwise returns the Ok value of self . |
and
和 and_then
在语义上其实非常类似,如果是 Ok 的话再做进一步的处理。不过 and 的参数是进一步处理的 结果,而 and_then 的参数是进一步处理的 函数,也就是说 and_then 可以起到 延迟计算 的效果。如果 and 的进一步处理有一定的代价的时候,可以考虑选择 and_then。or
和 or_else
的关系也是完全类似的。
除此外还有 map
, map_or
, map_err
, mar_or_else
, unwrap
, unwrap_or
, unwrap_or_else
,可以帮你把错误处理处理出花儿来。
Panic
默认情况(std)下,panic将打印失败消息、展开、清理堆栈并退出。通过环境变量,您还可以让 Rust 在发生 panic 时显示调用堆栈,以便更轻松地追踪恐慌的来源。
不过程序是允许我们自定义 panic handler 的,虽然一般情况不会用到这个特性,
在no_std环境下我们必须自定义 Panic handler,方法也很简单,参考https://doc.rust-lang.org/nomicon/panic-handler.html。
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
let mut host_stderr = HStderr::new();
// logs "panicked at '$reason', src/main.rs:27:4" to the host stderr
writeln!(host_stderr, "{}", info).ok();
loop {}
}
std 环境中想要自定义panic handler,可以使用std::panic::set_hook
方法。
可以借助 color-eyre 的帮忙。
Crates
anyhow
anyhow
是 Rust 语言中一个非常受欢迎的错误处理库,它提供了一种方便的方式来处理错误,特别是在那些不需要特别复杂错误处理的场景中(例如顶层的简单应用)。
它提供了anyhow::Error
类型。使用上类似Box<dyn std::error::Error>
,任何实现了std::error::Error
的类型都可以用它代替(因为实现了AsRef
?)。anyhow::Result<T>
= Result<T, anyhow::Error>
。因此,最简单的用法就是将返回值声明为anyhow::Result
,然后碰到Result
就?
。
use anyhow::Result;
fn get_cluster_info() -> Result<ClusterMap> {
let config = std::fs::read_to_string("cluster.json")?;
let map: ClusterMap = serde_json::from_str(&config)?;
Ok(map)
}
有2种进阶的用法,一个是在原先的Error上添加信息供使用者调试,一种是简单的创建一个新的Error。
anyhow::Context
提供了两个方法(lazy or not)来给Result添加额外信息。anyhow!("XXX error")
可以用来简单的创建一个Error。
thiserror
thiserror
更适合用于库的开发,其中错误类型通常需要更明确和结构化。thiserror
提供了一个宏,帮助定义和实现与错误相关的 trait,使得错误处理更加规范和易于维护。
- 自定义错误类型:
thiserror
允许开发者定义自己的错误类型,每种类型可以关联特定的数据和上下文,这有助于更精确地控制错误处理和传播。 - 自动实现
std::error::Error
: 使用thiserror
的#[derive(Error)]
宏,可以自动为自定义错误类型实现std::error::Error
trait,简化了错误实现的复杂性。 - 错误消息模板:
thiserror
支持在错误定义中直接使用格式化字符串作为错误消息,这些消息可以包含关联数据的字段,从而提供丰富的错误信息。 - 透明的错误转发:
thiserror
提供了#[error(transparent)]
属性,允许错误类型透明地包装其它错误类型,使得原始错误类型和消息可以直接传递出去。
### color eyre
color-eyre
是一个 Rust 库,用于改进错误报告的体验,非常适合用于需要高质量错误报告的应用程序。color-eyre
提供了以下几个关键特性,使得它在错误报告方面表现出色:
- 彩色和格式化的错误输出:
color-eyre
使用彩色和样式化的文本来增强错误消息的可读性。这包括为不同的错误信息部分使用不同的颜色和格式,如错误的类型、源文件、行号等。 - 详细的错误链输出:与
anyhow
和eyre
类似,color-eyre
支持错误链,允许开发者查看导致当前错误的原始错误。color-eyre
以一种易于追踪的方式格式化这些错误链。 - 可配置性:
color-eyre
提供多种配置选项,使开发者可以调整错误报告的样式和内容,如启用或禁用彩色输出,或者更改报告的各个部分的样式。 - 回溯支持:
color-eyre
可以集成 Rust 的回溯信息,即在发生错误时的函数调用堆栈。这对于调试和确定错误发生原因至关重要。