创建简洁和通用错误类型时遇到问题



我很难尝试创建一个既通用又不会演变成一堆impl和泛型参数的错误类型。

我有一个结构,其中包含一些子系统的通用参数,每个参数都有返回自定义错误类型的方法:

struct App<A: ..., B: ..., ...> {
a: A,
b: B,
...
}
impl<A: ..., B: ..., ...> App<A, B, ...> {
fn do_something_cross_cutting(&self) -> Result<(), AppError> {
a.run()?;
b.run()?;
Ok(())
}
}

应该如何明确定义AppError?起初我尝试使用这样的quick_error!

quick_error! {
#[derive(Debug)]
enum AppError<A: ..., B: ..., ...> {
AError(err: A::Error) {
from()
display(...)
}
BError(err: B::Error) {
from()
display(...)
}
...
}
}

但是quick_error!似乎不支持通用枚举。此外,由于AppError现在是泛型的,并且看起来不可能在impl块中定义类型别名,因此每个可能失败的方法都将具有非常广泛的返回类型(Result<_, AppError<A, B, ...>>)。

quick_error!可以避免,但同样以牺牲可读性和代码大小为代价,它不能解决第二个问题。

我想出了以下替代方案,但没有编译:

quick_error! {
#[derive(Debug)]
enum AppError {
AError(err: Box<Error>) {
display(...)
}
BError(err: Box<Error>) {
display(...)
}
...
}
}
impl<A: ...> From<A::Error> for AppError {
fn from(err: A::Error) -> Self {
AppError::AError(Box::new(err))
}
}
...

rustc抱怨the type parameter `A` is not constrained by the impl trait, self type, or predicates,我不知道如何解决它。

最后一种可能性是最不冗长的:简单地传播一个Box<Error>。这是我的B计划。最大的问题是丢失了有用的信息。通过定义AppError(以及递归地定义特定于子系统的错误类型),我得到了一个穷人的错误回溯。使用Box<Error>会使错误难以跟踪。

还有其他选择吗,还是我以错误的方式接近这一点?

错误the type parameter `A` is not constrained by the impl trait, self type, or predicates是什么意思?让我们看一个简化的示例:

trait Foo {
type Error;
}
struct Bar;
struct Baz;
impl Foo for Bar {
type Error = std::io::Error;
}
impl Foo for Baz {
type Error = std::io::Error;
}
struct Quux;
impl<T: Foo> From<T::Error> for Quux {
fn from(err: T::Error) -> Quux {
Quux
}
}

问题是类型U可以匹配多个TT::Error。例如,在这里,std::io::Error将同时匹配Bar::ErrorBaz::Error。那么,编译器应该为T选择哪种类型呢?

解决方案很简单:不要使用A::ErrorB::Error;相反,直接定义AB作为错误类型。当您在App中使用AppError时,您将返回AppError<A::Error, B::Error>而不是AppError<A, B>。是的,您必须在任何地方重复::Error;我认为没有解决方案。

最后,我使用了一个自定义错误类型,该类型检索构造回溯(使用backtrace箱)并将实际错误存储为Box<Error>

#[derive(Debug)]
pub struct AppError {
err:   Box<Error + Send + Sync>,
trace: Vec<String>,
}
impl AppError {
pub fn new<E: Into<Box<Error + Send + Sync>>>(err: E) -> Self {
let mut trace = Vec::new();
// Get backtrace
AppError {
err: err.into(),
trace,
}
}
}
impl Display for AppError {...}
impl Error for AppError {...}

唯一的皱纹是AppErrorError,我不能有毯子impl<E: Error> From<E> for AppError。相反:

impl<E: AppErrorTag> From<E> for AppError {...}
trait AppErrorTag: Into<Box<Error + Send + Sync>> {}

然后impl AppErrorTag for x {}每个相关的错误类型x.

有了这个,错误类型是简洁的(简单地AppError),通用的(只是标记相关类型的问题),并且有一个回溯。缺点是无法解构和检查错误。由于一致性规则,对标签的需求可能有点痛苦,特别是对于外部板条箱上的错误。真正理想的解决方案是使用负特征边界,但我们还没有达到目标。

相关内容

最新更新