11 个版本
0.1.19 | 2020 年 12 月 31 日 |
---|---|
0.1.12 | 2020 年 12 月 30 日 |
0.0.2 | 2020 年 11 月 29 日 |
#359 在 调试
在 3 个包中使用
48KB
715 行
witcher
使用简洁的错误处理跟踪和记录错误
通过使用 unwrap
和 expect
变体,或者费力地编写自定义枚举包装器,或者必须与杂乱的 Box
类型一起工作,来避免代码中随机终止执行,并通过 Result<T>
从 witcher 中作为返回类型,轻松包装错误并自动向上传播堆栈中的额外上下文消息。witcher 实现了 std::error::Error
保留向下转换和链式操作。最好的是,witcher 提供了圣杯:自动简化的回溯
。
你将获得
- 简化的错误处理
通过提供错误上的类型匹配
通过自动处理转换
通过提供简洁和简明的用户交互
通过提供条件彩色输出 - 能够讲述完整故事的错误处理
通过实现
std::error::Error
通过将错误链接在一起
通过提供上下文消息
通过提供从源头跟踪 - 安全性
100% 安全的代码,没有任何
unsafe
使用
零低级 TraitObject 操作
测试覆盖率超过 90%,经过良好测试
快速链接
宣言
来自Go语言的背景,我原本完全预期可以像Go语言的pkg/errors那样直接导入Rust的默认错误处理包,然后就可以大步前行。然而,随着我深入挖掘,我发现了一个丰富的、层层叠叠的人类学历史,有成千上万的项目和作者都在宣称他们高尚的理想和原则,他们都在试图解决同一个问题。Rust的错误处理功能还不够完善。这让人想起了Go语言在存在/errors
之前的样子。我发现了一些明显比其他项目更常用的项目,并看到了一些曾经流行的包的势头转变。然而,经过数周的研究和测试了无数不同的模式和包之后,我仍然没有找到像那个值得尊敬的pkg/errors一样简单易用的东西。因此,witcher
应运而生。
顺便提一下,当我深入探索时,我将所有关于低级别TraitObject操作的研究都转移到了phR0ze/rust-examples,我很高兴地说witcher
是100%安全的代码。
使用方法
使用wrap
扩展方法在Result
类型上包装错误,并附加额外的上下文信息,自动将错误连在一起。由于wrap
返回一个Result<T>
,所以符号更少,输入也更少。
需要rustc >= 1.30
这个最低rustc要求是由对Rust的std::error::Error
处理改进所做的增强所推动的。
- 在您的
Cargo.toml
中导入witcher并保留调试符号[dependencies] witcher = "0.1" [profile.release] debug = true
- 使用witcher预览
use witcher::prelude::*;
- 使用Result别名作为返回类型
fn do_something() -> Result<()>;
- 使用wrap扩展方法在Result上提供上下文
fn do_something() -> Result<()> { do_external_thing().wrap("Failed to slay beast") } fn do_external_thing() -> std::io::Result<()> { Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))? }
颜色
颜色自动由gory根据tty
检测控制。您可以通过将环境变量TERM_COLOR
设置为false值来手动禁用颜色,请参阅gory文档关于控制使用。
$ TERM_COLOR=0 cargo run -q --example simple
降级
我们可以使用降级或使用match_err!
宏来对错误类型进行匹配。
downcast_ref
- 访问std::error::Error的downcast_ref
use witcher::prelude::*;
// Wrap our internal error with additional context as we move up the stack
fn do_something() -> Result<()> {
do_external_thing().wrap("Failed to slay beast")
}
// Function that returns an external error type outside our codebase
fn do_external_thing() -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))?
}
fn main() {
let err = do_something().unwrap_err();
// Get the last error in the error chain which will be the root cause
let root_cause = err.last();
// Match single concrete error type
if let Some(e) = root_cause.downcast_ref::<std::io::Error>() {
println!("Root cause is a std::io::Error: {}", e)
} else {
println!("{}", err)
}
}
结果
$ cargo run -q --example downcast_ref
Root cause is std::io::Error: Oh no, we missed!
match_err!
- 匹配具体错误类型
use witcher::prelude::*;
fn do_something() -> Result<()> {
do_external_thing().wrap("Failed to slay beast")
}
fn do_external_thing() -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))?
}
fn main() {
let err = do_something().unwrap_err();
// Match multiple downcasted cases to handle errors differently
match_err!(err.last(), {
x: Error => println!("Root cause is witcher::Error: {}", x),
x: std::io::Error => println!("Root cause is std::io::Error: {}", x),
_ => println!("{}", err)
});
}
结果
$ cargo run -q --example downcast_match
Root cause is std::io::Error: Oh no, we missed!
pass
- 透明地传递错误
在某些情况下,可能希望能够使用Witcher常见的错误模式,但将特定的顶级错误作为传递。
警告:这不会与使用类型直接匹配而不是间接匹配Error类型的match_err!
宏一起工作。
use witcher::prelude::*;
fn do_something() -> Result<()> {
do_external_thing().pass()
}
fn do_external_thing() -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))?
}
fn main() {
let err = do_something().unwrap_err();
// Since we used `pass` we can match on the error directly
match err.downcast_ref::<std::io::Error>() {
Some(err) => println!("Root cause is std::io::Error: {}", err),
None => println!("Root cause is witcher::Error: {}", err),
}
}
结果
$ cargo run -q --example pass
Root cause is std::io::Error: Oh no, we missed!
链式
我们可以继续利用std::error::Error的source
方法来链式错误。第一个包装的错误将保留其具体类型,但链中的后续错误已丢失该信息。
source
- std::error::Error的source方法被公开
use witcher::prelude::*;
#[derive(Debug)]
struct SuperError {
side: SuperErrorSideKick,
}
impl std::fmt::Display for SuperError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SuperError is here!")
}
}
impl std::error::Error for SuperError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.side)
}
}
#[derive(Debug)]
struct SuperErrorSideKick;
impl std::fmt::Display for SuperErrorSideKick {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SuperErrorSideKick is here!")
}
}
impl std::error::Error for SuperErrorSideKick {}
fn do_something() -> Result<()> {
do_external_thing().wrap("Failed doing super hero work")
}
fn do_external_thing() -> std::result::Result<(), SuperError> {
Err(SuperError {side: SuperErrorSideKick})
}
fn main() {
if let Err(err) = do_something() {
// Traverse the error chain
let mut source = Some(err.std());
while let Some(err) = source {
match_err!(err, {
// Using alternate form of display for `Error` to get just the message
x: Error => println!("Found witcher::Error: {:#}", x),
x: SuperError => println!("Found SuperError: {}", x),
x: SuperErrorSideKick => println!("Found SuperErrorSideKick: {}", x),
_ => println!("unknown")
});
source = err.source();
}
}
}
结果
$ cargo run -q --example chain
Found witcher::Error: Failed doing super hero work
Found SuperError: SuperError is here!
Found SuperErrorSideKick: SuperErrorSideKick is here!
重试
我们可以使用几个不同的Result
扩展函数来重试失败的代码。
err_is
- 如果存在错误并且是给定类型,将返回true
use witcher::prelude::*;
fn retry_on_concreate_error_type_using_err_is() -> Result<()> {
let mut retries = 0;
let mut result = do_external_thing();
while retries < 3 && result.err_is::<std::io::Error>() {
retries += 1;
println!("retrying using err_is #{}", retries);
result = do_external_thing();
}
result.wrap("Failed while attacking beast")
}
fn do_external_thing() -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))
}
fn main() {
println!("{}", retry_on_concreate_error_type_using_err_is().unwrap_err());
}
结果
cargo run -q --example retry_err_is
retrying using err_is #1
retrying using err_is #2
retrying using err_is #3
error: witcher::Error: Failed while attacking beast
cause: std::io::error::Error: Oh no, we missed!
symbol: retry_err_is::retry_on_concreate_error_type_using_err_is
at: examples/retry_err_is.rs:11:5
symbol: retry_err_is::main
at: examples/retry_err_is.rs:18:22
retry_on
- 是一种更简洁的以类似方式执行的方式,如我们的err_is示例
use witcher::prelude::*;
fn retry_on_concreate_error_type() -> Result<()> {
do_external_thing().retry_on(3, TypeId::of::<std::io::Error>(), |i| {
println!("std::io::Error: retrying! #{}", i);
do_external_thing()
}).wrap("Failed while attacking beast")
}
fn do_external_thing() -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))
}
fn main() {
println!("{}", retry_on_concreate_error_type().unwrap_err());
}
结果
cargo run -q --example retry_on
std::io::Error: retrying! #1
std::io::Error: retrying! #2
std::io::Error: retrying! #3
error: witcher::Error: Failed while attacking beast
cause: std::io::error::Error: Oh no, we missed!
symbol: retry_on::retry_on_concreate_error_type
at: examples/retry_on.rs:4:5
symbol: retry_on::main
at: examples/retry_on.rs:16:22
retry
- 与retry_on
类似,但不考虑错误的类型
use witcher::prelude::*;
fn retry() -> Result<()> {
do_external_thing().retry(3, |i| {
println!("std::io::Error: retrying! #{}", i);
do_external_thing()
}).wrap("Failed while attacking beast")
}
fn do_external_thing() -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "Oh no, we missed!"))
}
fn main() {
println!("{}", retry().unwrap_err());
}
结果
cargo run -q --example retry
std::io::Error: retrying! #1
std::io::Error: retrying! #2
std::io::Error: retrying! #3
error: witcher::Error: Failed while attacking beast
cause: std::io::error::Error: Oh no, we missed!
symbol: retry::retry
at: examples/retry.rs:4:5
symbol: retry::main
at: examples/retry.rs:16:22
显示
Witcher的Error
类型为每个Display
格式选项实现不同的功能。它们遵循Witcher的详细程度,从最少信息到最多信息,即{} {:#} {:?} {:#?}
Normal: {}
- 将只输出第一条错误消息
Alternate: {:#}
- 将输出链中的所有错误消息
Debug: {:?}
- 将输出所有错误消息,并带有简化的回溯
Alternate Debug: {:#?}
- 将输出所有错误消息,并带有简化的回溯
贡献
欢迎提交拉取请求。然而,请理解,它们将纯粹根据这些更改是否符合我的项目目标/理念进行评估。
Git钩子
启用git钩子以实现自动版本增量
cd ~/Projects/witcher
git config core.hooksPath .githooks
许可证
该项目许可协议为以下之一
- MIT许可证 LICENSE-MIT 或 http://opensource.org/licenses/MIT
- Apache许可证,版本2.0 LICENSE-APACHE 或 http://www.apache.org/licenses/LICENSE-2.0
贡献
除非您明确说明,否则您有意提交给本项目并按Apache-2.0许可证定义的贡献,将按上述方式双重许可,不附加任何额外条款或条件。
待办事项
- 添加 Rust 文档注释
- 转换为 JSON 的机制可能使用以下代码:
{:#?}
更新日志
- 12/30/2020
- 修正了所需的最低
rustc
徽章
- 修正了所需的最低
依赖项
~2.4–3.5MB
~72K SLoC