Create a sentinel error by declaring a package-level variable of type error and assigning it a value created with errors.New(). This allows you to compare errors using the == operator instead of string matching, ensuring type-safe and reliable error handling across your codebase.
Here is the standard pattern for defining and using a sentinel error:
package mypkg
import "errors"
// Define the sentinel error at the package level
var ErrNotFound = errors.New("item not found")
func FindItem(id int) (string, error) {
if id < 0 {
return "", ErrNotFound
}
return "item", nil
}
In your calling code, check for this specific error using equality comparison. This is the idiomatic Go way to handle known error conditions:
package main
import (
"fmt"
"myproject/mypkg"
)
func main() {
item, err := mypkg.FindItem(-1)
if err == mypkg.ErrNotFound {
fmt.Println("Item was not found, handling gracefully")
return
}
if err != nil {
fmt.Println("Unexpected error:", err)
return
}
fmt.Println("Found:", item)
}
Sentinel errors are distinct from error types (structs implementing error) because they are simple values. You should use them for common, expected conditions like "not found," "already exists," or "timeout." If you need to attach context or metadata to an error (like the specific ID that wasn't found), you should define a custom error type instead of a sentinel. However, for simple flow control where the error type itself is the signal, sentinels are the most efficient and readable approach.
Avoid creating new errors with errors.New() inside functions and comparing them directly, as each call creates a new distinct error object. Always define the sentinel once at the package level and reuse that variable reference. This ensures that err == ErrNotFound evaluates to true regardless of where the error was returned from within the package.