anyerr
是一个动态错误处理库,提供了一个叫做 AnyError
的类型拥有表示错误,与 anyhow
类似但又有所不同,主要是在功能上更加丰富。大家如果喜欢,欢迎来 GitHub 来给个 star。
特性
anyerr
的各种特性可以总结为以下几点:
- 错误包装:各错误处理库的基本功能。提供了 helper trait 来简化错误包装过程。
- 错误类型:本 crate 中又称 error kind。
AnyError
可以携带 error kind,而 error kind 上可定制的,可以使用 crate 中预定义的,也可以自定义。 - 上下文信息:
AnyError
可以携带除了 error message 和 error kind 以外的额外的上下文信息,一般表示为一系列 key-value pairs。储存上下文信息的数据结构也可以定制,本 crate 提供了具有不同的特性的变种。 - 调用堆栈捕获:基本功能。如果 RUST_BACKTRACE 和 RUST_LIB_BACKTRACE 的值指定可以捕获堆栈,则必定会捕获。
安装
在 Cargo.toml
中添加
[dependencies]
anyerr = "0.1.1"
基本使用方法
定制自己的错误类型
上面提到了 AnyError
是可定制的,事实上,AnyError
是 AnyError<C, K>
,C
代表储存上下文的数据结构,K
是要使用的错误类型。
在自己的 crate 中使用 anyerr
时,一般会基于 AnyError<C, K>
来定制出自己的错误类型,然后在整个 crate 内使用。比如:
// Make this module accessible to your whole crate.
mod err {
use anyerr::AnyError as AnyErrorTemplate;
use anyerr::context::LiteralKeyStringMapContext; // A predefined context storage
pub use anyerr::{Intermediate, Overlay}; // Helper traits.
pub use anyerr::kind::DefaultErrorKind as ErrKind; // A predefined error kind.
pub use anyerr::Report; // An error reporting utility.
pub type AnyError = AnyErrorTemplate<LiteralKeyStringMapContext, ErrKind>;
pub type AnyResult<T> = Result<T, AnyError>;
}
接下来使用的不再是 anyerr::AnyError
,而是自己定制的 err::AnyError
。
错误的构造和使用
以下展示了如何构造一个 AnyError
。
use err::*;
fn fail() -> AnyResult<()> {
// Use `AnyError::minimal()` to create a simple [`String`]-based error.
Err(AnyError::minimal("this function always fails"))
}
fn check_positive(x: i32) -> AnyResult<()> {
if x > 0 {
return Ok(());
}
// Use `AnyError::quick()` to quickly create an error with an error
// message and an error kind.
Err(AnyError::quick(
"expects `x` to be a positive number",
ErrKind::ValueValidation
))
}
fn try_add_username(
usernames: &mut Vec<String>,
new_username: String
) -> AnyResult<usize> {
let res = usernames.iter()
.enumerate()
.find(|(_, username)| **username == new_username)
.map(|(index, _)| index);
if let Some(index) = res {
// Use `AnyError::builder()` to create an error with all essential
// context you'll need.
let err = AnyError::builder()
.message("the username already exists")
.kind(ErrKind::RuleViolation)
.context("new_username", new_username)
.context("index", index)
.build();
Err(err)
} else {
usernames.push(new_username);
Ok(usernames.len() - 1)
}
}
fn parse_i32(input: &str) -> AnyResult<i32> {
// Use `AnyError::wrap()` to wrap any other error type.
input.parse::<i32>().map_err(AnyError::wrap)
}
以上面的 try_add_username()
为例,假设我们拿到了其返回值,则我们可以这样处理:
use err::*;
fn main() {
let mut usernames = Vec::new();
let res = try_add_username(&mut usernames, "foo").unwrap();
assert_eq!(res, 0);
let err = try_add_username(&mut usernames, "foo").unwrap_err();
assert_eq!(err.to_string(), "the username already exists"); // Or `err.message()`.
assert_eq!(err.kind(), ErrKind::RuleViolation);
assert_eq!(err.get("new_username"), Some("\"foo\""));
assert_eq!(err.get("index"), Some("0"));
}
错误包装
以一个模拟访问数据库的代码片段为例子,展示如何使用 Overlay
和 Intermediate
helper trait 来包装一个已有的错误。
use err::*;
struct UserRepository {
conn: Arc<Connection>,
}
impl UserRepository {
pub fn find_by_username(&self, username: &str) -> AnyResult<User> {
// Don't build SQL statements yourself in practice.
let statement = format!("SELECT * FROM users WHERE users.username = '{username}'");
let data = self.conn.query(&statement)
.overlay(("could not get a `User` due to SQL execution error", ErrKind::EntityAbsence))
.context("username", username)
.context("statement", statement)?;
let entity = User::try_from(data)
.overlay(("could not get a `User` due to serialization error", Errkind::EntityAbsence))
.context("username", username)?;
Ok(entity)
}
}
Overlay::overlay()
用于一个 AnyError<C, K>
或 Result<T, AnyError<C, K>>
上,用于指定构造一个新的 AnyError
并将原有的错误包装进去,Overlay::overlay()
的参数可以传入 error message 和 error kind(具体看文档),然后其返回一个中间状态或者说是 builder,可以继续调用 Intermediate::context()
来添加上下文信息,最后借助 ?
传播包装后的错误。
错误报告
考虑如下这样嵌套多次的错误,借助 Report
可以方便地打印错误信息,无论是程序运行中打印到 logger 还是作为程序错误结束的报告,都是可以的:
use err::*;
fn source_error() -> AnyResult<()> {
let err = AnyError::builder()
.message("the source error is here")
.kind(ErrKind::InfrastructureFailure)
.context("key1", "value1")
.context("key2", "value2")
.build();
Err(err)
}
fn intermediate_error() -> AnyResult<()> {
source_error()
.overlay("the intermediate error is here")
.context("key3", "value3")?;
Ok(())
}
fn toplevel_error() -> AnyResult<()> {
intermediate_error()
.overlay("the toplevel error is here")?;
Ok(())
}
fn main() -> impl Termination {
Report::capture(|| {
toplevel_error()?;
Ok(())
})
}
完整示例
以下是一个超简单的 echo server 的例子,其中一些部分为了展示功能,可能会比正常写法略复杂:
mod err {
use anyerr::context::LiteralKeyStringMapContext;
use anyerr::AnyError as AnyErrorTemplate;
pub use anyerr::kind::NoErrorKind as ErrKind;
pub use anyerr::Report;
pub use anyerr::{Intermediate, Overlay};
pub type AnyError = AnyErrorTemplate<LiteralKeyStringMapContext, ErrKind>;
pub type AnyResult<T> = Result<T, AnyError>;
}
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::process::Termination;
use std::thread;
use std::time::Duration;
use err::*;
const SERVER_IP: &str = "127.0.0.1";
const SERVER_PORT: &str = "8080";
fn main() -> impl Termination {
Report::capture(|| {
let listener = TcpListener::bind(format!("{SERVER_IP}:{SERVER_PORT}"))
.map_err(AnyError::wrap)
.overlay("could not bind the listener to the endpoint")
.context("ip", SERVER_IP)
.context("port", SERVER_PORT)?;
eprintln!("Started listening on {SERVER_IP}:{SERVER_PORT}");
for connection in listener.incoming() {
let Ok(stream) = connection else {
continue;
};
thread::spawn(move || {
handle_connection(stream).unwrap_or_else(|err| {
let report = Report::wrap(err).kind(false);
eprintln!("{report}");
});
});
}
Ok(())
})
.kind(false)
}
fn handle_connection(mut stream: TcpStream) -> AnyResult<()> {
let client_addr = stream
.peer_addr()
.map_or("<UNKNOWN>".into(), |addr| addr.to_string());
let mut buffer = [0u8; 256];
let mut total_read = 0;
eprintln!("{client_addr} started the connection");
thread::sleep(Duration::from_secs(3));
loop {
let size_read = stream
.read(&mut buffer)
.map_err(AnyError::wrap)
.overlay("could not read bytes from the client")
.context("client_addr", &client_addr)
.context("total_read", total_read)?;
total_read = size_read;
if size_read == 0 {
eprintln!("{client_addr} closed the connection");
return Ok(());
}
thread::sleep(Duration::from_secs(3));
let mut cursor = 0;
while cursor < size_read {
let size_written = stream
.write(&buffer[cursor..size_read])
.map_err(AnyError::wrap)
.overlay("could not write bytes to the client")
.context("client_addr", &client_addr)
.context("total_read", total_read)
.context("cursor", cursor)?;
cursor = size_written;
}
}
}
进阶用法
更多用法和 API 请参考 Docs.rs。
评论区
写评论可以展示Stack backtrace这个功能太棒啦