Errors derived from constants in Go

Ralph Ligtenberg
Travix Engineering
Published in
2 min readOct 18, 2019

--

A recent post from the Go Programming Language Blog shows us how we should deal with error comparison. But what about constant errors?

Photo by Daniel Reche from Pexels

This was the first question that popped up in my head as soon as I finished reading. Dave Cheney’s talk about constant errors raised the importance that it’s bad practice to declare errors as variables. Yet, the samples in the blog post are using variables, not constants.

So let’s see if we can come up with a proper example using constants instead of variables. The first step is easy: we’ll start with defining a new error type:

type NotFoundError stringfunc (e NotFoundError) Error() string {
return string(e)
}

And the constant declaration:

const ErrNotFound NotFoundError = "not found"

At first sight you’d think the naming is a bit weird, but that is because of conventions: types are suffixed with “Error”, while constants are prefixed with “Err”. So we’re just following the conventions here.

Next, we can define a derived type which can contain additional properties:

type ItemNotFoundError struct {
Name string
}
func (e ItemNotFoundError) Error() string {
return fmt.Sprintf("%s %s", e.Name, ErrNotFound)
}

If we want to use this new type as an error, then we can declare a variable in our code as follows:

var err error = ItemNotFoundError{Name: "My item"}

Whenever this new error is returned by a method, the caller should be able to check the error using the new error methods, right? Let’s try with errors.Is():

if errors.Is(err, ErrNotFound) {
// Do something
}

But if you run it, it doesn’t work, because when our error is unwrapped, the underlying type cannot be derived. To solve this, we need to implement the Unwrap() method:

func (e ItemNotFoundError) Unwrap() error {
return ErrNotFound
}

Besides using errors.Is(), it’s also possible to use errors.As():

var err2 NotFoundErrorif errors.As(err, &err2) {
// Do something with err2
}

But there’s not much sense in doing that, since the resulting error is the same as our constant.

Also, keep in mind that our error constant is derived from string, which makes it possible to use it like this:

fmt.Println("constant: " + ErrNotFound)
// prints "constant: error not found"

But we cannot do that for our derived error type, since it’s a struct instead of a string:

var err error = ItemNotFoundError{Name: "My item"}fmt.Println("errors.Is(): " + err)
// won't compile (mismatched types string and error)

That’s all I wanted to share about errors for now. :-)

As you might have seen, I’m using value receivers instead of the pointer receivers as used for the examples in the Go blog post, because there’s no reason to use pointer semantics in this case.

If you’re interested in the source used for this article, here’s a Gist containing the full example:

--

--

Ralph Ligtenberg
Travix Engineering

Growth leader, people coach, idea catalyst, process optimizer, Agile advocate, Rubberduck, Boyscout Rule practitioner. Intentional extravert.