I'm working on a graceful shutdown library for tokio, and part of that is error handling.
I give the user the choice for which type is used for error wrapping, which is here called WrapperType. Then, he can pass any error through it that implements Into<WrapperType> into the library.
WrapperType of course has to be some kind of Error itself. My current use cases are limited to WrapperType being one of T: std::error::Error, Box<dyn Error>, anyhow::Error and miette::Error.
Now here comes the problem: When an error gets passed through my library, I want to retrieve its description (no problem) and its source (that's where the problem comes in).
I simplified the code of the library to the following minimal example:
use std::error::Error;
#[derive(Debug)]
struct WrappedError<WrapperType: ToString>(WrapperType);
fn wrap<WrapperType, ErrType>(err: ErrType) -> WrappedError<WrapperType>
where
ErrType: Into<WrapperType>,
WrapperType: ToString,
{
WrappedError(err.into())
}
impl<T: ToString> WrappedError<T> {
fn get_description(&self) -> String {
self.0.to_string()
}
fn get_source(&self) -> Option<&(dyn Error + 'static)> {
/* this is the problem */
todo!();
}
}
// This is just a dummy error type to demonstrate errors with
// and without a source
#[derive(thiserror::Error, Debug, miette::Diagnostic)]
enum MyError {
#[error("An error")]
AnError,
#[error("An indirect error")]
Indirect(#[source] Box<dyn Error + Send + Sync>),
}
fn main() {
wrap::<MyError, _>(MyError::AnError);
wrap::<MyError, _>(MyError::Indirect("Real error.".into()));
wrap::<anyhow::Error, _>(MyError::AnError);
wrap::<anyhow::Error, _>(MyError::Indirect("Real error.".into()));
wrap::<anyhow::Error, _>(anyhow::anyhow!("Anyhow error."));
wrap::<miette::Error, _>(MyError::AnError);
wrap::<miette::Error, _>(MyError::Indirect("Real error.".into()));
wrap::<miette::Error, _>(miette::miette!("Miette error."));
wrap::<Box<dyn Error>, _>(MyError::AnError);
wrap::<Box<dyn Error>, _>(MyError::Indirect("Real error.".into()));
wrap::<Box<dyn Error>, _>(anyhow::anyhow!("Anyhow error."));
wrap::<Box<dyn Error>, _>(miette::miette!("Miette error."));
// All of those are errors of some kind.
// I'd like to retrieve a textual description and the error source from them
// through the WrappedError.
let err: WrappedError<MyError> = wrap(MyError::Indirect("Real error.".into()));
println!("Description: {}", err.get_description());
println!("Source: {:?}", err.get_source());
}
Now there's a couple of things I've tried, but most of them run into one of those problems:
- Rust does not yet have generic specialization
anyhow::Error,miette::ErrorandBox<dyn Error>do not implementstd::error::Error- You cannot specialize an impl for
Box<dyn Error>andT where T: std::error::Errorat the same time, because the compiler saysBox<dyn Error>might one day implementstd::error:Error, even though that will never be the case - It's impossible to create an
implblock for types that have eitherstd::error:ErrororDeref<Target = std::error::Error>
The textual description, on the other hand, works without a problem, because all four types implement ToString and therefore WrapperType: ToString is possible.
However, getting the source is a problem, because the four types require different methods to do that. On the surface, they look identically:
let e: MyError = MyError::AnError;
e.source();
let e: anyhow::Error = MyError::AnError.into();
e.source();
let e: miette::Error = MyError::AnError.into();
e.source();
let e: Box<dyn Error> = MyError::AnError.into();
e.source();
Under the hood, they are actually different:
let e: MyError = MyError::AnError;
std::error::Error::source(&e);
let e: anyhow::Error = MyError::AnError.into();
std::error::Error::source(&*e);
let e: miette::Error = MyError::AnError.into();
std::error::Error::source(&*e);
let e: Box<dyn Error> = MyError::AnError.into();
std::error::Error::source(&*e);
Meaning, the other error types only work because they implement Deref<Target = dyn Error>.
Sadly, it's impossible to say that WrapperType should be either Deref<Target = dyn Error> or std::error::Error.
I tried implementing a trait to solve the issue, but sadly, Deref<Target = dyn Error> and std::error::Error collide:
trait GetSource {
fn get_source_internal(&self) -> Option<&(dyn Error + 'static)>;
}
impl<T: ToString> GetSource for WrappedError<T>
where
T: Deref<Target = dyn Error + Send + Sync + 'static>,
{
fn get_source_internal(&self) -> Option<&(dyn Error + 'static)> {
(&*self.0).source()
}
}
impl<T: ToString> GetSource for WrappedError<T>
where
T: std::error::Error,
{
fn get_source_internal(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
}
conflicting implementations of trait `GetSource` for type `WrappedError<_>`
So I'm officially out of ideas on how to achieve this. Any help is welcome.