What should be put inside an inner Error enum?

Hi folks,

I learned about error handling, how to implement Error and Display traits for my custom error enums and how to implement From trait so I can use ?. And I started using thiserror so I don't need to do any of that :wink:

What I'm not sure is what to add to the inner (the most low-level) enum. In all examples, I find people just moving system errors into a parent enum but in my case, I have some parsing/text manipulation functions that use MyError1 like this:

#[derive(thiserror::Error, Clone, Debug, Eq, PartialEq)]
enum MyError1 {
    #[error("foo")]
    FirstError,
    #[error("bar")]
    SecondError,
}

#[derive(thiserror::Error, Clone, Debug, Eq, PartialEq)]
enum MyError2 {
    #[error("abcdef")]
    ThirdError(MyError1),
    #[error("12345")]
    FourthError(MyError1),
}

// custom From since I want to map one enum to another and thiserror can't do that
impl From<MyError1> for MyError2 {
    fn from(err: MyError1) -> Self {
        match err {
            MyError1::FirstError => MyError2::ThirdError(err),
            MyError1::SecondError => MyError2::FourthError(err),
        }
    }
}

and I'm not sure what to put inside.

In other words should I put a String into the MyError1?

enum MyError1 {
    #[error("foo")]
    FirstError(String),
    #[error("bar")]
    SecondError(String),
}

and then

match x {
    "" => MyError1::FirstError("the input was empty")
    ...
}

or maybe a custom struct FirstError(CustomStruct)?

Thank you.

You should put in information you have about the specific error situation. For example, if you failed to open a file, you might have a PathBuf for the file path and a std::io::Error for the specific error.

You should not put in a String unless the error is about that string in some way, or you're wrapping an error message you got from an outside source (like an error message from a subprocess you ran) that you don't know anything else about the structure of.

Since you say you are parsing text, you might put in the text that failed to parse, and that would be a String, but you might also choose to put in a position or span — the range of characters, or line numbers, in the original input, rather than the text itself. This is usually more useful as a basis for producing good error messages than only the erroneous text itself, because people troubleshooting parse errors need to know the context.

4 Likes

Thank you @kpreid

That's the thing - there is no external source. Those functions are pure text in, Result<String, Err> out.

So something like this?

enum ParsingError {
    #[error("empty input")]
    EmptyInput(ParsingErrorDetails),
    #[error("wrong characters")]
    WrongCharacter(ParsingErrorDetails),
}

struct ParsingErrorDetails {
    line_number: i32,
    raw_input: String,
}

Rather than paraphrasing or making my own example, I'll just link to this article about creating great Rust errors. It's longer form than a forum comment, but worth reading.

1 Like