Go Sentinel Errors
A sentinel error is a package-level error value that code returns for one named failure. Callers test for that exact value instead of reading the message text.
A package-level sentinel error
ErrTooSmall is declared once at package level. check returns that exact value for a small number.
package main
import (
"errors"
"fmt"
)
var ErrTooSmall = errors.New("value too small")
func check(n int) error {
if n < 10 {
return ErrTooSmall
}
return nil
}
func main() {
err := check(4)
fmt.Println(err)
fmt.Println(errors.Is(err, ErrTooSmall))
}errors.As extracts a custom error type
LimitError carries the limit and the given value as fields. errors.As copies the error into a variable so the fields can be read.
package main
import (
"errors"
"fmt"
)
type LimitError struct {
Limit int
Got int
}
func (e LimitError) Error() string {
return fmt.Sprintf("value %d is over the limit %d", e.Got, e.Limit)
}
func check(n int) error {
if n > 100 {
return LimitError{Limit: 100, Got: n}
}
return nil
}
func main() {
err := check(140)
var limitErr LimitError
if errors.As(err, &limitErr) {
fmt.Println("Limit:", limitErr.Limit)
fmt.Println("Got:", limitErr.Got)
}
}Is for the sentinel and As for the type
One check function can return either failure shape. errors.Is tests the sentinel case and errors.As tests the typed case.
package main
import (
"errors"
"fmt"
)
var ErrTooSmall = errors.New("value too small")
type LimitError struct {
Limit int
Got int
}
func (e LimitError) Error() string {
return fmt.Sprintf("value %d is over the limit %d", e.Got, e.Limit)
}
func check(n int) error {
if n < 10 {
return ErrTooSmall
}
if n > 100 {
return LimitError{Limit: 100, Got: n}
}
return nil
}
func main() {
fmt.Println(errors.Is(check(4), ErrTooSmall))
var limitErr LimitError
fmt.Println(errors.As(check(140), &limitErr))
fmt.Println(limitErr.Got)
}Example
package main
import (
"errors"
"fmt"
)
var ErrTooSmall = errors.New("value too small")
type LimitError struct {
Limit int
Got int
}
func (e LimitError) Error() string {
return fmt.Sprintf("value %d is over the limit %d", e.Got, e.Limit)
}
func gate(n int) error {
if n < 10 {
return ErrTooSmall
}
if n > 100 {
return LimitError{Limit: 100, Got: n}
}
return nil
}
func main() {
err := gate(4)
if errors.Is(err, ErrTooSmall) {
fmt.Println("too small")
}
err = gate(150)
var limitErr LimitError
if errors.As(err, &limitErr) {
fmt.Println("over limit by", limitErr.Got-limitErr.Limit)
}
}Key points
- Use a package-level var with errors.New to declare a sentinel error.
- A function returns the sentinel value itself for its named failure.
- Use errors.Is to test an error against a sentinel value.
- A custom error type carries extra fields about a failure.
- Use errors.As to copy a custom error value out of an error into a variable.
- errors.Is answers which named failure happened.
- errors.As answers what extra details a failure carries.
In this example, gate returns the sentinel for the value 4. errors.Is matches that sentinel, so main prints too small. Gate then returns a LimitError for 150, and errors.As copies it into limitErr. The last print uses the two fields and shows the value went 50 over the limit.
Note
A sentinel error is matched by its value, not by its message text.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Go Playground
Exercise
Which call extracts a custom error type and its fields from an error?