Arash Taher

Making errors.Is Simpler with Sentinels

Here’s an amendment to my previous post on Go error handling that I wrote a year ago. Since then, my knowledge of Go has grown significantly, and I now have a better understanding of how things work.

The problem I faced with the solution suggested in that post is that, as it turned out, if you don’t have an error constant that you can easily compare errors with, using AppError can become cumbersome: each time you have to convert the error with As and check if it’s an AppError, and that’s not ideal.

Instead, here’s what you can do: We will have our AppError as before with the same methods and fields.

type AppError struct {
    ErrorCode    string
    ErrorMessage string
    innerError   error
}

func (e *AppError) Error() string {
    return e.innerError.Error()
}

func (e *AppError) Unwrap() error {
    return e.innerError
}

Now here’s the important step: We will define a variable for a specific type of error:

var ErrFetchDataApp = &AppError{
    ErrorCode: FetchDataAppErrorCode,
}

This variable will be accessible by importers of this package.

The next step is to make sure we can create this app error with a custom message and inner error. For that, we will have a New function:

func NewFetchDataAppError(message string, err error) error {
    return &AppError{
        ErrorCode:    FetchDataAppErrorCode,
        ErrorMessage: message,
        innerError:   err,
    }
}

Important note: We shouldn’t modify the global ErrFetchDataApp variable directly, as this could cause race conditions in concurrent code. Instead, we create a new instance each time.

Everywhere that we need to return FetchDataAppError, we will use this function, and the beautiful part is that now we can easily check for the error type without any type conversion: errors.Is(err, ErrFetchDataApp).

No more checking for ErrorCode or using errors.As.

Final thoughts

This solution brings me closer to the style I was aiming for:

That said, I’m still learning. Maybe there are tradeoffs here that I haven’t seen yet. For example, will this pattern hold up well if I end up with dozens of error codes? I’d love to hear from other Go developers: would you prefer this sentinel-marker style, or stick with the Is method approach?

Tagged: #Go #Error Handling #Programming

Subscribe to Newsletter