Get `Error::source` from a generic that might be either `std::error::Error`, `Box<dyn Error>` or `anyhow::Error`

Viewed 36

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::Error and Box<dyn Error> do not implement std::error::Error
  • You cannot specialize an impl for Box<dyn Error> and T where T: std::error::Error at the same time, because the compiler says Box<dyn Error> might one day implement std::error:Error, even though that will never be the case
  • It's impossible to create an impl block for types that have either std::error:Error or Deref<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.

0 Answers
Related