errors.Is vs errors.As — Specification¶
Table of Contents¶
- Introduction
- The errors Package: Public API
- Behavior of
errors.Is - Behavior of
errors.As - Behavior of
errors.Unwrap - Behavior of
errors.Join - The
Unwrap/Is/AsMethod Contracts - Behavior Matrix
- Panic Conditions
- Compatibility Across Versions
- Things the Spec Does NOT Define
- References
Introduction¶
This document collects the formal contracts of errors.Is, errors.As, errors.Unwrap, and errors.Join, along with the optional method interfaces (Unwrap, Is, As) that user types may implement to participate. It covers what is documented and stable, what is panic-inducing, and what the standard library leaves unspecified.
Reference: pkg.go.dev/errors, Go 1.13 release notes, Go 1.20 release notes.
The errors Package: Public API¶
package errors
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.
//
// Unwrap returns nil if the Unwrap method returns []error.
func Unwrap(err error) error
// Is reports whether any error in err's tree matches target.
//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling Unwrap. When err wraps multiple errors, Is examines err followed by a
// depth-first traversal of its children.
//
// An error is considered to match a target if it is equal to that target or if it
// implements a method `Is(error) bool` such that Is(target) returns true.
//
// An error type might provide an Is method so it can be treated as equivalent
// to an existing error.
func Is(err, target error) bool
// As finds the first error in err's tree that matches target, and if one is found,
// sets target to that error value and returns true. Otherwise, it returns false.
//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling Unwrap. When err wraps multiple errors, As examines err followed by a
// depth-first traversal of its children.
//
// An error matches target if the error's concrete type is assignable to the value
// pointed to by target, or if the error has a method `As(any) bool` such that
// As(target) returns true.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
func As(err error, target any) bool
// Join returns an error that wraps the given errors. Any nil error values are
// discarded. Join returns nil if every value in errs is nil.
//
// The error formats as the concatenation of the strings obtained by calling
// the Error method of each element of errs, with a newline between each string.
//
// A non-nil error returned by Join implements the `Unwrap() []error` method.
func Join(errs ...error) error
// ErrUnsupported indicates that a requested operation cannot be performed,
// because it is unsupported. (Added in Go 1.21.)
var ErrUnsupported = New("unsupported operation")
Behavior of errors.Is¶
Inputs and outputs¶
- Returns true if any error in
err's tree matchestarget. - Returns false otherwise, including when:
erris nil andtargetis non-nil.targetis nil anderris non-nil.- Returns true when both
errandtargetare nil. - Never panics under normal use. (See "Panic Conditions" for edge cases.)
Walk algorithm¶
- If
target == nil, returnerr == nil. - Compute
isComparable := reflect.TypeOf(target).Comparable(). - Loop:
- If
isComparable && err == target, return true. - If
errimplementsIs(error) boolanderr.Is(target)is true, return true. - If
errimplementsUnwrap() error:- Set
err = err.Unwrap(). If now nil, return false. Else continue loop.
- Set
- If
errimplementsUnwrap() []error:- For each child, recursively call
Is(child, target). If any returns true, return true. - Return false.
- For each child, recursively call
- Otherwise return false.
Match rules¶
| Rule | Match condition |
|---|---|
| Default equality | err == target, only when target's dynamic type is comparable. |
Custom Is | err.(interface{ Is(error) bool }).Is(target) returns true. |
A node matches if either rule fires. Custom Is fires after default equality, so it cannot mask a literal equality match — but it can broaden matching beyond ==.
Behavior of errors.As¶
Inputs and outputs¶
- Returns true if a matching error was found, and
*targetis set to that error. - Returns false otherwise.
- Returns false when
erris nil. - Panics when
targetis nil, not a pointer, or a pointer to a type that neither implementserrornor is an interface type.
Walk algorithm¶
- Validate
target: targetmust be non-nil.targetmust be a pointer (reflect.Ptr).- The pointer must be non-nil.
*target's type must implementerroror be an interface type.- If any validation fails, panic.
- Loop:
- If the dynamic type of
erris assignable totargetType:- Set
*target = err(via reflection). - Return true.
- Set
- If
errimplementsAs(any) boolanderr.As(target)returns true:- Return true. (The
Asmethod is responsible for setting*target.)
- Return true. (The
- If
errimplementsUnwrap() error:err = err.Unwrap(). If now nil, return false. Else continue loop.
- If
errimplementsUnwrap() []error:- For each child, recursively call
As(child, target). If any returns true, return true. - Return false.
- For each child, recursively call
- Otherwise return false.
Match rules¶
| Rule | Match condition |
|---|---|
| Default assignability | reflect.TypeOf(err).AssignableTo(reflect.TypeOf(target).Elem()). |
Custom As | err.(interface{ As(any) bool }).As(target) returns true. |
A node matches if either rule fires. Custom As fires after default assignability.
What target can be¶
*TwhereTimplementserror. Example:var pe *os.PathError; errors.As(err, &pe).*IwhereIis an interface type (typically containingerror). Example:var t Temporary; errors.As(err, &t).
What target cannot be: - T (not a pointer). - nil. - *int, *string, etc. — types that neither implement error nor are interfaces.
Behavior of errors.Unwrap¶
- Returns the result of calling
err.Unwrap() error, if such a method exists. - Returns nil if
erris nil. - Returns nil if
errdoes not implementUnwrap() error. - Returns nil if
erronly implementsUnwrap() []error(the multi-error variant). - Does NOT walk the chain. It returns the immediate next link, not the deepest cause.
To get the deepest cause, loop:
Behavior of errors.Join¶
- Returns nil if all
errsare nil (or iferrsis empty). - Otherwise returns a non-nil error wrapping all non-nil entries.
- The returned error's
Error()method concatenates each child'sError()joined by"\n". - The returned error implements
Unwrap() []errorreturning the original (non-nil)errsslice. - The returned error does not implement
Unwrap() error.errors.Unwrap(joined)returns nil. - A single-arg
Join(err)(with err non-nil) does return a wrapper, noterritself.
Examples:
errors.Join() // nil
errors.Join(nil) // nil
errors.Join(nil, nil) // nil
errors.Join(io.EOF) // wrapper around [io.EOF], not io.EOF
errors.Join(io.EOF, io.ErrUnexpectedEOF) // wrapper around [io.EOF, io.ErrUnexpectedEOF]
errors.Join(nil, io.EOF, nil, errFoo) // wrapper around [io.EOF, errFoo]
The Unwrap/Is/As Method Contracts¶
Unwrap() error¶
- Returns the immediate next error in the chain.
- Returns nil at the end of the chain.
- Should not return the receiver (cycle).
- Should be deterministic: the same call returns the same error each time.
Unwrap() []error¶
- Returns the slice of immediate child errors.
- The returned slice should not be mutated by callers (it is shared with the wrapper).
- Returning an empty slice is allowed (matches a node with no children).
- A nil entry in the slice is permitted; the walk will skip nil children gracefully.
- Should be deterministic: the same call returns the same slice.
Is(target error) bool¶
- Returns true if
e(the receiver) should be considered equivalent totarget. - Should be a pure function of
eandtarget(no side effects). - Need not be symmetric:
a.Is(b)may differ fromb.Is(a). - Need not be reflexive:
e.Is(e)is decided by the implementer. - The receiver may be nil; the method should handle nil receivers safely.
As(target any) bool¶
- Inspects
target(which is a pointer; check its element type via type-switch). - If the receiver can fill
*target, sets*targetand returns true. - Returns false if no fill is possible. Does not modify
*targeton false. - Should not panic on unexpected
targettypes — return false instead. - The receiver may be nil; the method should handle nil receivers safely.
A typical implementation:
func (e *MyErr) As(target any) bool {
switch t := target.(type) {
case **MyErr:
*t = e
return true
case *KindCode:
*t = e.Code
return true
}
return false
}
Behavior Matrix¶
| Input shape | errors.Is returns | errors.As returns |
|---|---|---|
Is(nil, nil) / As(nil, &x) | true / false | (see comparable) |
Is(err, nil), err ≠ nil | false | n/a |
Is(nil, target) | false | false |
Is(err, target), exact match at depth 0 | true | true |
Is(err, target), match after Unwrap | true | true |
Is(err, target), target is non-comparable, no custom Is | false (silent) | (n/a) |
As(err, &concrete), err's type assignable to concrete | true, sets target | (matches) |
As(err, &iface), err implements iface | true, sets target | (matches) |
As(err, target) with target == nil | n/a | panic |
As(err, target) with non-pointer target | n/a | panic |
As(err, target) with pointer to non-error non-interface | n/a | panic |
Panic Conditions¶
errors.Is does not panic in any documented case. (It can panic if a custom Is method panics, but that is the method's fault.)
errors.As panics if and only if:
target == nil. Message:errors: target cannot be nil.reflect.ValueOf(target).Kind() != reflect.Ptr. Message:errors: target must be a non-nil pointer.reflect.ValueOf(target).IsNil(). Same message.targetTypedoes not implementerrorand is not an interface. Message:errors: *target must be interface or implement error.
The panic happens before the chain walk; you do not get a partial walk.
errors.Join does not panic.
errors.Unwrap does not panic.
fmt.Errorf panics if %w is used with a non-error argument.
Compatibility Across Versions¶
| Feature | Introduced in | Stable since |
|---|---|---|
errors.New | Go 1.0 | always |
errors.Is | Go 1.13 | 1.13 |
errors.As | Go 1.13 | 1.13 |
errors.Unwrap | Go 1.13 | 1.13 |
fmt.Errorf("%w", err) | Go 1.13 | 1.13 |
Unwrap() []error interface | Go 1.20 | 1.20 |
errors.Join | Go 1.20 | 1.20 |
fmt.Errorf with multiple %w | Go 1.20 | 1.20 |
errors.ErrUnsupported | Go 1.21 | 1.21 |
The Go 1 compatibility promise covers the package; the function signatures and documented behavior will not change in incompatible ways within Go 1.x.
A library that wants to support both single-error Unwrap and multi-error Unwrap can implement both methods. The standard library will detect both interfaces; the type switch in errors.Is/errors.As checks Unwrap() error first, then Unwrap() []error.
Things the Spec Does NOT Define¶
- Cycle behavior: if
Unwrapchains form a cycle,errors.Isanderrors.Asloop forever. The spec does not promise detection. - Order of
Isvs default equality: documented as default equality first, then customIs. A reasonable implementation could change this; do not rely on internal ordering for correctness. Asmethod's effect on a false return: callers should treat*targetas undefined whenAsreturns false. The standard library does not modify*targetwhen its default rule does not match, but a buggy customAsmight.- Performance characteristics: not part of the spec. They are de facto stable but not guaranteed.
- Stack-trace capture:
errors.Is/errors.Asdo not capture stack traces. Adding stack traces requires a custom error type or third-party package. - Concurrency: nothing is said about concurrent use. The walk reads
Unwrap,Is,Asmethods; if those are concurrent-safe, the walk is. The standard library types (*fmt.wrapError,*errors.errorString) are immutable and thus safe.
References¶
- Package errors
- Package fmt — Errorf
- The Go Programming Language Specification (does not define error wrapping; refers to the
errorspackage) - Go 1.13 release notes — Error wrapping
- Go 1.20 release notes — Multiple wrapped errors
- Russ Cox, "Error values proposal"
- Russ Cox, "Working with Errors in Go 1.13"