Error types - as enums or as structs


#1

Well after starting to work on my new rust project, I came to the inevitable fate of having to handle errors from multiple libraries, which implied defining a custom error type.

The obvious thing would be to create an enum that collects the possible error types, just as hyper does:
https://hyper.rs/hyper/v0.10.5/hyper/error/enum.Error.html

But while inspecting the errors of the other libraries I am using I found that the standard library and serde define an enum and then wrap it into a struct with a single field, which seems pointless to me.
https://docs.serde.rs/src/serde_json/error.rs.html#13-15

I’m sure there is a valid reason for it and I would appreciate if someone could let me know.


#2

This is probably to keep the error somewhat opaque outside the module - in your serde example, the struct is public but the enum is private. This effectively means code outside the module cannot destructure the Error - it serves as a “generic” catch-all for all errors where Error is returned. This versions better too since you can add/remove variants to the private enum without impacting code outside the module.

If you expose an enum as the public error type, it’s much harder to version and evolve, as well as providing more fine grained error information.

This is my guess of the above - I’m by no means speaking authoritatively for any of the libs you mentioned.


#3

Also, consider using error-chain, it makes defining custom errors wrapping other crates errors super easy.

Although, as a learning exercise it could pay to implement it yourself manually first, then see how much boilerplate error-chain can let you drop from your library :smile:

error-chain also defines a struct wrapping an enum, but unlike serde the enum is public and intended to be used by consumers. The reason it uses a struct is so it can store extra data along with the actual error, including a (very useful) stacktrace when running in debug mode.


#4

I can tell you about the serde_json case. There are two relevant reasons:

  • The Box wrapper decreases the size of the error type from 56 bytes (ErrorImpl) to 8 bytes (Error). Due to how many functions in serde_json pass around Result<T, Error>, we benchmarked the smaller error type as a 5% performance improvement over the unboxed one. Astonishingly that means without Box, serde_json would be wasting 1 out of every 20 units of work it does on shuffling around Result bytes from one spot to another.

    Note that before making such an optimization you should be sure to have a benchmark suite that you trust as being representative of real use cases and that is sensitive enough to reveal performance differences of a few percent.

  • The private enum allows us to add and remove error variants as we refactor and optimize our code. It gives us precise control over how users interact with the information contained in errors and it enables us to select an API that we are comfortable supporting long-term.


#5

Thanks for all your awesome responses!:slight_smile: