Is it a good idea to have anyhow::Error in a public type?

Hello everyone,

When wiring multiple fallible traits I tend to end up with something like:

enum Error<A>{
    // ... concrete crate errors ...
    WrappedError(A),
}

But when there are multiple type parameters things start looking weird and makes downstream interaction cumbersome, especially since we don't have associated impl types yet. So a fn Result becomes this strange thing:

pub fn do_something(&self) -> Result<(), Error<TypeParamA, TypeParamB>> {
   // ...
}

One way out was something along the lines of std::io::{Error,ErrorKind}, but I find that rather unpleasant compared to erasing the type using anyhow::Error.

Case in point: foca's Error enum. It used to have the wrapped errors (Encode/Decode/CustomBroadcast) with explicit types and using it was super cumbersome downstream...

Using anyhow::Error doesn't sound like a terrible idea: the type is super lean and there's now std::error::Error so it shouldn't become an integration challenge like failure was...

So, am I missing something or it is perfectly fine to have anyhow's Error in a public type?

There's certain advice I see going around that anyhow is a good choice for binaries, and thiserror is a good choice for libraries. Are you familiar with the latter?

It's not generally advisable to erase error types in libraries, as it's hard to predict what your clients might want to handle how.

3 Likes

Oh yeah, thiserror is pretty good for deriving the traits necessary to implement error, but it doesn't apply for my case: it's about propagating errors that I don't have full control of.

Perhaps the fact that Foca is no_std with optional std support and expects users to implement other traits to use it helps paint the picture better?

It sits in a space that's almost an application but acts like a library: users implement a Codec trait, for example, and Foca uses the codec, emitting foca::Error::Encode(CodecError).

Fair point and I agree. I'm thinking that in my case is not too bad to do so because there's a top level Error enum with a discriminant that wraps each error I don't control, so users should be able to downcast_ref::<RealErrorType>() with certainty. Is that still too annoying?

Could you please elaborate?
thiserror has a #[from] macro to wrap errors from other libraries.

In that case, it is advisable to treat the library as a library, and your binary as a "user".

1 Like

Thank you all for your input so far!

Sure thing. All thiserror does is implement other traits for me (From<SrcError> in the case of the attribute your pointed out). This isn't what I need because I don't know what the error is at that point.

std now has std::error::Error trait which would allow me to wrap any error I don't control, but then I would only be able to have a single discriminant. Something like:

pub enum Error {
  // ...
  Other(#[from] std::error::Error)),
}

But since there are more than one kind of "other" error (and no std::error::Error in no_std), it ends up clunky.

Maybe this code in the playground helps: Rust Playground

I don't understand what you mean here. It's still a library, there's no main function.

I mean, if the error type is exposed as a public API, it's good to use your own custom error type rather anyhow::Error. This opnion is irrespective of the fact whether you internally use the error, either in another part of the same library, in another library or in a binary.

Would something like the following help?

enum MyError {
    BugOne,
    BugTwo,
    First(Box<dyn Error>),
    Second(Box<dyn Error>),
}

Ah, I see. So it would apply if I were only using anyhow::Error in the result type.

That's not very different from what I've asked about (or tried to :sweat_smile: ) which is:

enum MyError {
    BugOne,
    BugTwo,
    First(anyhow::Error),
    Second(anyhow::Error),
}

Is it? In fact, I'd expect anyhow's to perform/fit better than the two indirections.

In my case Box<dyn std::error::Error> isn't good enough because we don't have the trait in no_std, so I went for anyhow::Error. But it's in the same spirit as using your suggestion.

Ah yes, in a no_std environment, it is actually a nice idea to use anyhow::Error, given your constraints.

Gotcha. Thanks a lot for your time!

I should've used more code and less words from the get go heh... I'll try it in the future.

Cheers

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.