16

I have a type Foo whose methods may "raise" errors of an associated type Foo::Err.

pub trait Foo {
    type Err;
    
    fn foo(&mut self) -> Result<(), Self::Err>;
}

I have another trait Bar with a method intended to process a Foo. Bar may issue errors of its own (specified by an associated type Bar::Err), but it may also encounter errors generated by the Foo it is processing.

I can see two ways to do this, but I don't know which one would be the most idiomatic to Rust.

The first one embeds a result in a result:

pub trait Bar1 {
    type Err;
    
    fn bar<F: Foo>(&mut self, foo: F) -> Result<Result<F, F::Err>, Self::Err>;
}

The second one merges the two error types into a dedicated enum:

pub trait Bar2 {
    type Err;
    
    fn bar<F: Foo>(&mut self, foo: F) -> Result<F, Choice<F::Err, Self::Err>>;
}

The second one looks semantically cleaner, but creates some hurdles for handling the additional enum.

playground

Emerson Harkin
  • 889
  • 5
  • 13
Pierre-Antoine
  • 1,915
  • 16
  • 25

2 Answers2

16

Typically you don't do a "merge", but instead use nested errors, like this.

enum IntError {
    Overflow,
    Underflow
}

enum StrError {
    TooLong,
    TooShort,
}

enum GenericError {
    Int(IntError),
    Str(StrError),
}

impl From<IntError> for GenericError {
    fn from(e: IntError) -> Self {
        GenericError::Int(e)
    }
}

impl From<StrError> for GenericError {
    fn from(e: StrError) -> Self {
        GenericError::Str(e)
    }
}
hellow
  • 12,430
  • 7
  • 56
  • 79
  • 3
    Sorry if my example wasn't clear; when I wrote about "merging the two error *types* into a dedicated enum", what I had in mind was exactly something like your `GenericError`. My question was: is this additional layer the right way to go, or should I rather use a result of result?... – Pierre-Antoine Sep 23 '18 at 09:56
5

You should use a trait object Error, and you return the first error that you encounter:

pub trait Bar {
    fn bar<F: Foo>(&mut self, foo: F) -> Result<F, Box<dyn Error>>;
}

or implement your trait like this:

impl Bar for MyType {
    type Err = Box<dyn Error>;

    fn bar<F: Foo>(&mut self, foo: F) -> Result<F, Self::Err>;
}

If you really want to have your two errors (but this is strange because one error suffices to make the process not ok), you can use a crate like failure to create an "error trace".

As a general advice, you should not forget to use the traits from std to add more semantic to your code.

Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • @trentcl Oh, you're right, Err is useless after that :P Thanks for the remark. – Boiethios Sep 21 '18 at 12:50
  • Good point about the Error trait, that's indeed something that I must use. And indeed, this opens the third option of returning a Box. However, that option does not seem very useful for the caller. They can not match it agains different error variants to decide what to do next... – Pierre-Antoine Sep 23 '18 at 10:04
  • 2
    *use a crate like failure* — note that failure is deprecated. See [How do you define custom `Error` types in Rust?](https://stackoverflow.com/q/42584368/155423) for a list of selected alternatives. – Shepmaster Jun 30 '20 at 14:27