liveBook · Manning

Sentinel error

As explained in Go in Action, Second Edition

A sentinel error is a predefined error value identified by comparison so callers can make programmatic decisions about a failure condition.

A sentinel error is a predefined, unique error value representing a known condition. Packages commonly expose these values with names beginning with Err, allowing callers to make consistent programmatic decisions instead of matching message strings. section 7.7 section 7.8

Use sentinel errors when a caller needs to recognize a recurring condition:

  • Define package-level values such as ErrConflict and ErrNotExist. section 7.7
  • Return the same value whenever the corresponding condition occurs. section 7.7
  • Check with errors.Is so handling remains valid through wrapped errors. section 7.8
  • Add a custom error type when the failure also needs variable data, such as missing keys. section 7.9

A key-value store

A KVStore can return ErrConflict when Put encounters an existing key and ErrNotExist when Get or Delete cannot find one. ForcePut uses errors.Is(err, ErrConflict) to delete the old value and retry, while wrapped errors preserve the underlying cause. section 7.7 section 7.8

Listing 7.10 main.go: Sentinel errors
type KVStore struct {
	m    map[string]string
}
func NewKVStore() *KVStore {
	m := make(map[string]string)
	return &KVStore{
		m: m,
	}
}

var ( #1
	ErrConflict error = errors.New("key already exists") #2
	ErrNotExist error = errors.New("no value exists for key")
)

Inspect the package-level ErrConflict and ErrNotExist definitions used by the KVStore example. section 7.7

Listing 7.12 main.go: Using errors.Is to check for sentinel errors
type Store interface {
...
	ForcePut(key string, val string) error
}
...

func (store *KVStore) ForcePut(key string, val string) error {
	err := store.Put(key, val)
	if err == nil {
		return nil
	}
	if errors.Is(err, ErrConflict) { #1
		if err := store.Delete(key); err != nil {
			return errors.New("unable to delete as part of force put") #2
		}
		if err := store.Put(key, val); err != nil {
			return errors.New("unable to delete as part of force put)
		}
		return nil
	}
	return errors.New("unable to force put, unknown error")
}

Inspect ForcePut handling a conflict with errors.Is and a conditional retry. section 7.8

How Go in Action, Second Edition covers it

How deep it goes
Explains declaration, return, errors.Is checks, wrapping, and the point where custom error types carry extra data.
Examples
Uses KVStore operations, ErrConflict, ErrNotExist, io.EOF, ForcePut, and bulk retrieval of missing keys.
What it assumes
Assumes familiarity with Go's error interface and develops sentinel values before custom error-type inspection.
What you can do after
Apply it via errors.Is to recognize known conditions, including conditions preserved inside wrapped errors.
How it connects
Connects sentinel errors with the error interface, errors.Is, error wrapping, standard-library errors, and custom error types.

Also explained in

Compare how 3 books explain sentinel error →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Action, Second Edition. The section links point to the parts of the book it draws on.