errors.Is
and Custom Error Types
Go's errors.As
works as expected with custom error types. You don't need to do
anything special to make it work. But errors.Is
is different: by default, it
uses ==
to compare the error with the target (1).
This is OK for simple custom types that don't have struct fields, and it is particularly well suited to what Go calls "sentinel errors". A sentinel error is a well-known error value that you can use to know what an error is.
An example of sentinel error is
fs.ErrExist
:
But custom error types often include struct fields, such as:
The contents of CertPath
and Err
is variable; for example, it could be:
NOTE: If you are confused and think it should be
&CertPendingError{...}
instead, you can read the section below.
So, to make errors.Is
work with this particular error, you would have to match
it exactly:
It doesn't make sense… What you really want is to do:
As a workaround, people fall back to using errors.As
or a type assertion:
Or they default to the old way of comparing errors:
Go's errors
package allows us to "tweak" how errors.Is
works for custom
types that have struct fields. Since CertPath
and Err
are changing for each
error, we just ignore those fields:
But don't you want to match on the Code
too? It would allow you to create
specific sentinel errors for each error code. Let's do it:
This lets us write:
Go errors can be concrete struct values; it all depends on how you
implemented the Error() string
func. It is easy to get confused by the error
returned by the Go compiler when trying to use a struct value as an error. For
example, consider the following custom error:
The compiler will return:
This error misleads you in thinking that errors must be pointers. What it
actually means is that the Error() string
method is implemented over *Err
instead of Err
. The following code works:
That's because concrete values can implement interfaces.
Another reason that may lead you to think that errors must be pointers is the
fact that interface values can be nil
, making you believe that an error
needs to be a pointer.
This illusion comes from the fact that an interface is implemented as a pair:
a value and a type. For a concrete type implementing an interface, the pair
only has two possible combinations:
Value | Type |
---|---|
nil | none |
Err{} | Err |
The pair (value=nil, type=Err
) is impossible. So, with custom error based on
concrete structs, you cannot return a typed nil
value. The compiler will say:
But are typed pointers useful for custom errors? The answer is no! Let's take the following pointer-based custom error:
The usual err == nil
won't work with this typed nil
value:
To recap:
Most of the time, you will want to use a concrete receiver for your custom errors:
In some cases, you may need to use a pointer receiver, for example if some value contained is shared or really big: