错误处理
在 Zino 框架中,我们定义了一个通用的错误类型Error,主要目的是实现以下功能:
- 基于字符串将任意错误包装成同一类型;
- 支持错误溯源,并能追溯到原始错误;
- 支持
tracing,自动记录错误信息。
这三条需求对Zino框架至关重要,这也是为什么我们没有采用社区中流行的错误处理库,比如anyhow。
在实际应用开发中,我们往往并不会对具体的错误类型做不同的处理[^new_error],而是直接返回错误消息,
所以我们采取基于字符串的错误处理:
#[derive(Debug)]pub struct Error {message: SharedString,source: Option<Box<Error>>,}
其中SharedString是Zino中用来优化静态字符串处理的类型^benchmark。
我们可以调用sources方法返回一个迭代器进行错误溯源,也可以使用root_source方法来追溯到原始错误。
对于任意实现了std::error::Error trait的错误类型,我们可以将它转换为Error类型:
impl<E: std::error::Error> From<E> for Error {#[inline]fn from(err: E) -> Self {Self {message: err.to_string().into(),source: err.source().map(|err| Box::new(Self::new(err.to_string()))),}}}
这样在需要返回Result<T, zino_core::error::Error>的函数中,我们就可以很方便地使用?运算符。
需要注意的是,我们的Error类型本身并没有实现std::error::Error。
通过为Error类型实现std::fmt::Display,我们可以提供对tracing的集成,让它自动记录错误信息:
impl std::fmt::Display for Error {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {let message = self.message();if let Some(source) = &self.source {let source = source.message();let root_source = self.root_source().map(|err| err.message());if root_source != Some(source) {tracing::error!(root_source, source, message);} else {tracing::error!(root_source, message);}} else {tracing::error!(message);}write!(f, "{message}")}}
每当我们调用.to_string()时,tracing::error!就会自动生成一条记录。
[^new_error]: 当然,你也可以自定义可枚举的错误类型,并为其实现std::error::Error trait。
