I wanted a way to return an error value without losing any deeper error information. For example in my database code, I have an error I return when a get operation has failed e.g. ErrDatabaseGetFailed. This is independent of the store (bolt/inmem/etc) but I still want to return why it failed specifically, e.g. something went wrong with boltdb/inmem etc, whatever.
I wanted a way to do something like
if err == MyErr
and
if err == SpecificErr
or/and get further information to log. However, go only has the fmt error wrap, I can’t return an existing error value I’ve defined AND the deeper one in the same error so I came up with this.
This allows me to return a hierarchy of errors, both custom types and error values simply by
return Wrap(ErrDatabaseGetFailed, err)
the wrapped error is seen as ErrDatabaseGetFailed. You can’t compare it with ‘==’ but that’s probably not a good idea anyway, although I’ll admit I’ve been doing that until recently.
Perhaps there’s not one agreed-upon good way to do this yet, so there isn’t a standard way built into the Go standard library.
For example…
I learned Go after I learned Python and C#, so I wrote my own errors package that had Python’s concept of a __cause__ and a __context__, so my error type was essentially:
type myError struct {
err error
cause error
context error
}
Where err and cause behave similarly to your outerErr and innerErr, respectively, but there was an additional context field for any errors encountered while attempting to handle an error.
Later on, I wrote another errors subpackage for another project and that does away with the cause vs. context distinction and instead has an []error slice whose first error is the outermost error and the last is the innermost.
Some pros to this implementation are that:
It is easier for me to represent errors from functions that fan-out and fan-in results from multiple concurrent goroutines.
When iterating through deeply nested errors, it has better performance characteristics than iterating through 2-error field structs which behave like linked lists or like trees when iterating through them with errors.Is/errors.As.
However, one of the major cons to this is that wrapping errors essentially does:
Which can be slow to allocate and copy, and result in a lot of memory usage if something is keeping those inner error slices alive.
Tl;dr:
My point is that there are many ways to generate and propagate errors to callers and that they have trade-offs that may require different users to write their own error types that solve the problem differently. errors.As and errors.Is makes it possible to use these different error types together.
Would only allow me to annotate an existing error with additional information. I wanted to return an error from within an error to indicate where the error came from. For example, my database code is generic, I persist a type that contains a byte slice of data, on a higher level each repository type uses this code. By returning an error within an error I can trace exactly where that error came from but I could also have custom errors implement a Message() method to provide me with log information of what inputs could have caused that error in the first place. I could do this in my logging middleware and just tag that on as it returns.