Idiomatic way of returning a not-found error in Golang?

Issue

I have a function with this signature in Go:

func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error)

It returns an error if there’s an internal error (like the database query fails for some reason), but I’m not sure what I should return if the account is not found. I can think of two different approaches:

  1. Just return a nil account and nil error if no account is found
  2. Return a custom error type like this:
type accountNotFoundErr struct {
    id uuid.UUID
}

func (err accountNotFoundErr) Error() string {
    return fmt.Sprintf("account not found for user: %v", err.id)
}

func IsAccountNotFoundErr(err error) bool {
    _, ok := err.(accountNotFoundErr)
    return ok
}

func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error) {
    // if the account is not found
    return nil, accountNotFoundErr{id}
}

I like the first one because it’s simple, but I don’t often see Go code which returns a nil result if the error is non-nil. I think the expectation is that, if the error is nil, the result is valid. The second approach fixes that, but it’s also a bit more complicated for callers.

What is an idiomatic approach for handling cases like this in Go?

Solution

I have read a lot of posts about custom errors in go. Most of them created their own struct that implements the error interface.

The issue I found with that approach was that I did not manage to easily check if an error was of a certain type. The same way, you may be able to check some std lib error like if error == EOF.

Therefore, my favourite way to do that is creating a simple var with erros.New.

var ErrNotFound = errors.New("Resource was not found")

func main() {
    err := raise()
    if err == ErrNotFound {
        fmt.Println("impossibru")
        return
    }
    if err != nil {
        fmt.Println("unexpected error")
        return
    }
}

func raise() error {
    return ErrNotFound
}

https://play.golang.com/p/s0ZQfsdLqxB

As @Gavin pointed out in the comments, if you want to provide more context to the error by wrapping it with fmt.Errorf, you need to use errors.Is to check if the specific error was wrapped.

var ErrNotFound = errors.New("Resource was not found")

func main() {
    err := raise(42)
    if errors.Is(err, ErrNotFound) {
        fmt.Println(err)
        return
    }
    if err != nil {
        fmt.Println("unexpected error")
        return
    }
}

func raise(id int) error {
    return fmt.Errorf("id %d does not exist, error: %w", id, ErrNotFound)
}

https://play.golang.com/p/hSrkb1Xp4Hn

Answered By – The Fool

Answer Checked By – David Marino (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.