# Go's `errors.Is` and Custom Error Types
[TOC]
## Problem
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]).
[1]: https://github.com/golang/go/blob/b07b20fbb591ac77158e1089299ce5acad71ffde/src/errors/wrap.go#L55 "Is(err, target error) bool"
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`](https://github.com/golang/go/blob/b07b20fbb591ac77158e1089299ce5acad71ffde/src/io/fs/fs.go#L146):
```go
errors.Is(err, fs.ErrExist)
```
But custom error types often include struct fields, such as:
```go
type CertPendingError struct {
CertPath string
Code int
Descr string
}
```
The contents of `CertPath` and `Err` is variable; for example, it could be:
```go
err := CertPendingError{CertPath: `\VED\Policy\cert`, Code: 400, Descr: "Certificate does not exist"}
```
> **NOTE:** If you are confused and think it should be `&CertPendingError{...}` instead, you can read the section [below](#Go-Custom-Errors-Concrete-or-Pointers).
So, to make `errors.Is` work with this particular error, you would have to match
it exactly:
```go
errors.Is(err, CertPendingError{CertPath: `\VED\Policy\cert`, Code: 400, Descr: "Certificate does not exist"})
```
It doesn't make sense... What you really want is to do:
```go
errors.Is(err, CertPendingError{})
```
As a workaround, people fall back to using `errors.As` or a type assertion:
```go
errors.As(err, &CertPendingError{})
```
Or they default to the old way of comparing errors:
```go
_, ok := err.(CertPendingError)
```
## Solution
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:
```go
func (e CertPendingError) Is(target error) bool {
_, ok := target.(CertPendingError)
return ok
}
This lets us write:
```go
errors.Is(err, &CertificatePendingError{})
```
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:
```go
const (
ErrCertPending = CertPendingError{Code: 400}
ErrCertFailure = CertPendingError{Code: 600}
)
func (e CertPendingError) Is(target error) bool {
if target == nil {
return false
}
t, ok := target.(CertPendingError)
return ok && e.Code == t.Code
}
```
This lets us write:
```go
errors.Is(err, ErrCertPending)
```
## Go Custom Errors: Concrete or Pointers?
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:
```go
type Err struct{}
func (e *Err) Error() string { // ⚠️ Pointer receiver (e *Err)
return "this is an Err"
}
func Func() error {
return Err{} // ❌ Can't return a concrete value.
}
```
The compiler will return:
```text
main.go:12:9: cannot use Err{} (value of type Err) as error value in return statement: Err does not implement error (method Error has pointer receiver)
```
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:
```go
type Err struct{}
func (e Err) Error() string { // ✅ Concrete receiver (e Err)
return "this is an Err"
}
func Func() error {
return Err{} // ✅ Concrete value
}
```
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:
```go
err = (Err)(nil) // ❌ cannot convert nil to type Err
```
But are typed pointers useful for custom errors? The answer is no! Let's take
the following pointer-based custom error:
```go
type Err struct{}
func (e *Err) Error() string {
return "this is an Err"
}
```
The usual `err == nil` won't work with this typed `nil` value:
```go
func main() {
var err error
err = (*Err)(nil) // ✅
fmt.Println(err == nil) // ❌ The nil assertion no longer works!
}
```
To recap:
1. Most of the time, you will want to use a concrete receiver for your custom
errors:
```go
func (e Err) Error() string
```
2. In some cases, you may need to use a pointer receiver, for example if some
value contained is shared or really big:
```go
func (e *Err) Error() string
```