Introduction
Basic errors created with errors.New only carry a text message. Real applications often need richer error information, such as an error code, a field name, or the original cause. Go lets you define custom error types by implementing the Error() string method on any type. Combined with error wrapping (fmt.Errorf with %w), the standard library functions errors.Is and errors.As let calling code inspect an error chain to check for a specific sentinel error or extract a specific error type.
Cricket analogy: A basic errors.New is like a scoreboard simply flashing 'OUT', while a custom error type with fields is like the third umpire's report specifying it was an lbw review with a specific ball-tracking reason, and errors.Is/As let a captain check the exact dismissal type down the review chain.
Syntax
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Msg)
}
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// matched a sentinel error, possibly wrapped
}
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println(ve.Field)
}
Explanation
A custom error type is just a struct (or any type) with an Error() string method, satisfying the error interface. Sentinel errors are package-level error values, like var ErrNotFound = errors.New("not found"), used as known markers to compare against. errors.Is(err, target) walks the error chain (following wrapped errors created with %w) and reports whether any error in the chain equals the target, which is useful for sentinel errors. errors.As(err, &target) walks the chain looking for an error whose concrete type matches target, and if found, assigns it into target so you can access its fields, which is useful for custom error types like ValidationError.
Cricket analogy: A sentinel error like ErrNotFound is like a standing rule 'no result' declared for a rain-out, checked with errors.Is by comparing against that known outcome, while errors.As is like extracting the full match report (a ValidationError) to read specific details such as overs bowled when rain struck.
Example
package main
import (
"errors"
"fmt"
)
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Msg)
}
var ErrNotFound = errors.New("record not found")
func lookup(id int) error {
if id < 0 {
return &ValidationError{Field: "id", Msg: "must be non-negative"}
}
if id == 0 {
return fmt.Errorf("lookup failed: %w", ErrNotFound)
}
return nil
}
func main() {
err := lookup(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("matched sentinel: record not found")
}
err2 := lookup(-1)
var ve *ValidationError
if errors.As(err2, &ve) {
fmt.Println("invalid field:", ve.Field)
}
}
Output
matched sentinel: record not found
invalid field: id
Key Takeaways
- A custom error type is any type implementing Error() string.
- Sentinel errors are package-level error values used as comparison markers.
- errors.Is checks whether a wrapped error chain contains a specific sentinel error.
- errors.As extracts a specific error type from a wrapped chain into a target variable.
- fmt.Errorf with %w is what makes an error chain traversable by errors.Is/As.
- Custom error types can carry structured data like field names or error codes.
Practice what you learned
1. What is required for a type to be usable as a custom error?
2. What does errors.Is(err, target) do?
3. What does errors.As(err, &target) do when it succeeds?
4. What makes an error chain traversable by errors.Is and errors.As?
5. What is a sentinel error in Go?
Was this page helpful?
You May Also Like
Error Handling in Go
Learn Go's idiomatic error handling using the built-in error interface and explicit error return values instead of exceptions.
panic, recover and defer in Go
Understand Go's defer, panic and recover mechanisms and when they should (and should not) replace normal error handling.
Interfaces in Go
Learn how Go interfaces define behavior through implicit, structural typing without classes or inheritance.
Structs in Go
A composite type that groups related fields together, forming the basis for custom data modeling in Go.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics