Refactoring Boilerplate Code with Multiple Trait Implementations

In a small library that I've been working on, I have an errors module. While the code works properly, I'm unhappy with how it's implemented; there's a LOT of boilerplate code. I would like to reduce it, but I'm not entirely sure what's the best way to do so. Here's a link to the code.

The module is fairly simple: there are three structs that encapsulate different errors that may happen within the library. These three structs are then themselves encapsulated by an enum, called ErrorKind. ErrorKind is what's returned by the library to the user, who then is expected to handle those errors.

My problem with this module is the amount of overhead involved when defining a new error. I have to define the struct and it's impl, along with implementing the different traits that go along with Error. Along with that, you have to implement the From trait on ErrorKind in order to create a new ErrorKind from the new struct. It's annoying, and I don't want to have to do it unless I have to. If I were to write this module in, say, Java, I'd simply create a base class that implements all of these different traits, which I would then extend when creating new errors objects.

Can anyone suggest an alternative way to implementing this module where there's less overhead? I apologize for the crappy code. I know it's clunky and terrible. Hence why I'm reaching out for help!

Are those error structs really necessary? I guess they are, since they are there. Then I would say that this is a typical situation where a macro could help. Something like

make_errors! {
    E1: ("something went wrong: {}", desc),
    E2: ("something else went wrong: {}", desc)
}

which would generate

pub enum ErrorKind {
    E1(E1),
    E2(E2)
}

//...

#[derive(Debug)]
pub struct E1 {
    desc: String
}

//and so on...

You might consider reducing the number of error types, e.g.,

pub enum ErrorKind {
    InvalidOption(String),
    MissingOptArg(String),
    MissingArgument(String),
}

Also, I usually like for errors to encode key data rather just error strings. For example, your MissingArgument doesn't need to contain anything. When you impl Display for your ErrorKind type, you can put the nice human readable message there.

Similarly for the others. Instead of storing the full human readable description, you might instead store the name of the invalid argument. It's not much practical difference in this case since you're still just storing a String, but the error will be much easier to use programmatically.

3 Likes

That's probably the best way forward. Considering the developer will really only interact with ErrorKind, it's probably best to make that the central piece of the module. Thank you for your answer!