Finomnis
Finomnis

Reputation: 22601

Miette - How to specify errors that contain a generic that defaults to Box<dyn Error>?

While this question is about the miette library, it could probably be answered by every experienced Rustacean by looking at the miette sourcecode. It isn't that big, but I wasn't able to extract the answer from it, sadly.

All the following examples assume the dependencies:

miette = { version = "4.4", features = ["fancy"] }
thiserror = "1.0"

I currently have a custom error definition similar to the following in my library tokio-graceful-shutdown:

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum MyError {
    #[error("Error containing another error")]
    AnError(#[source] Box<dyn Error + Send + Sync>),
}

I would like the source error type to be generic, with a default value of Box<dyn Error>.


My attempts

If I would leave out the miette::Diagnostic, the solution would look like this:

#[derive(Debug, thiserror::Error)]
pub enum MyError<ErrType = Box<dyn Error + Send + Sync>> {
    #[error("Error containing another error")]
    AnError(#[source] ErrType),
}

If I would leave out the Box<dyn Error> default value, the solution would look like this:

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum MyError<ErrType: 'static + Error> {
    #[error("Error containing another error")]
    AnError(#[source] ErrType),
}

Sadly, combining the two, it turns out that Box<dyn Error> does NOT implement Error:

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum MyError<ErrType: 'static + Error = Box<dyn Error + Send + Sync>> {
    #[error("Error containing another error")]
    AnError(#[source] ErrType),
}
|
| pub enum MyError<ErrType: 'static + Error = Box<dyn Error + Send + Sync>> {
|                                     ^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn std::error::Error + Send + Sync + 'static)`
= note: required because of the requirements on the impl of `std::error::Error` for `Box<(dyn std::error::Error + Send + Sync + 'static)>`

I suspect, however, that this error message is a red herring and instead, the 'static + Error is incorrect and ErrType needs a different restriction.

The reason why I suspect this is that if I directly use Box<dyn Error> as the source type, it works, as in the first example. That makes me believe that it isn't actually Error that miette::Diagnostic requires to work.


Final question

(which is hopefully not susceptible to the XY problem)

What should be inserted at the <???> placeholder to make the following code compile?

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum MyError<ErrType: <???> = Box<dyn Error + Send + Sync>> {
    #[error("Error containing another error")]
    AnError(#[source] ErrType),
}

Upvotes: 1

Views: 616

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 71025

For why this happens, see Why doesn't Box<dyn Error> implement Error? (by the way, this is not related to miette at all: the error is triggered even without it).

You can use a newtype wrapper to work around this problem:

pub struct BoxedError(pub Box<dyn Error + Send + Sync>);

impl fmt::Debug for BoxedError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

impl fmt::Display for BoxedError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl Error for BoxedError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.0.source()
    }
    #[allow(deprecated)]
    fn description(&self) -> Option<&str> {
        self.0.description()
    }
    #[allow(deprecated)]
    fn cause(&self) -> Option<&dyn Error> {
        self.0.cause()
    }
}

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum MyError<ErrType: 'static + Error = BoxedError> {
    #[error("Error containing another error")]
    AnError(#[source] ErrType),
}

Upvotes: 3

Related Questions