The standard library gives us only very basic error capabilities. For the most part, we can create strings-as-errors:
return errors.New("oh no")
And we can wrap errors with additional textual context:
if err := doSomething(); err != nil {
return fmt.Errorf("doSomething: %w", err)
}
But the fact that the built-in error
type is an interface gives us a ton more flexibility than this to include arbitrary information with our errors. There are a few ways we can include additional information in errors, and I’ll go over a few of them. Today I’ll start with an approach that’s quite common in the standard library, and elsewhere: Error structs.
Here’s a very simple example from the standard library’s net
package:
type AddrError struct {
Err string
Addr string
}
If we look at the source for the Error
method on this type, we see that it returns either Err
alone, or when the address is not blank, an error string that contains both the address and the error.
So what’s so special about this being a struct?
The fact that it’s a struct makes it very easy to deconstruct, and get at the address component alone, without resorting to string parsing:
err := doSomething()
var addrErr net.AddrError
if errors.As(err, &addrErr) {
fmt.Println("The error occurred when trying to access %s", addrErr.Addr)
}
If you need access to a component of the error, this approach sure beats trying to parse an error string! Not only is it easier, it’s less error prone, as the chances of a false positive are virtually none, and you don’t risk an error string representation changing when you upgrade the library that produces the error.