Skip to content

Readers

The functions that get something back out of an error.

Every one of them uses the same tree traversal, which visits layers outermost-first, depth-first through the members of an aggregate. That is why none of these can disagree with another about what an error contains, and why all of them see through a Join.

Hints and Details

func Hints(err error) []string
func Details(err error) []string

Return every hint (or detail) in the tree, outermost first, de-duplicated. An empty result is nil, not an empty slice.

err := errors.WithHint(errors.WithHint(errors.WithHint(
    errors.New("x"), "inner"), "middle"), "outer")

errors.Hints(err)   // [outer middle inner]

Identical strings collapse to one:

errors.Hints(errors.WithHint(errors.WithHint(errors.New("x"), "same"), "same"))
// [same]

Empty strings are dropped rather than returned.

The GetAll aliases

func GetAllHints(err error) []string
func GetAllDetails(err error) []string

Exactly Hints and Details, under the names the estate already called. They exist so migrating a module is an import-path change rather than a port; prefer the short names in new code.

FlattenHints and FlattenDetails

func FlattenHints(err error) string
func FlattenDetails(err error) string

Join the list into one string, separated by \n--\n:

err := errors.WithHint(errors.WithHint(errors.New("x"), "b"), "a")
errors.FlattenHints(err)   // "a\n--\nb"

The separator is deliberately distinctive so a flattened list can be split back out, which a newline-joined one cannot.

Attrs

func Attrs(err error) []slog.Attr

Returns every structured attribute in the tree, outermost first. Duplicate keys are preserved — see WithAttrs.

KindOf

func KindOf(err error) string

Returns the error's identity: the outermost kind that is not one of this package's own annotation wrappers. Returns "" when nothing in the tree declares one.

var ErrNotFound = errors.NewSentinel("forge.not_found", "provider not found")

errors.KindOf(ErrNotFound)                     // "forge.not_found"
errors.KindOf(errors.New("x"))                 // "errors.basic"
errors.KindOf(fmt.Errorf("plain"))             // ""
errors.KindOf(errors.Newf("reading %s", "f"))  // ""

Annotating does not change the answer, which is the whole point:

err := errors.WithAttrs(
    errors.WithHint(errors.WithStack(ErrNotFound), "h"),
    slog.Int("n", 1),
)
errors.KindOf(err)   // still "forge.not_found"

The outermost kind alone would report whichever annotation happened to be applied last — errors.attrs for that error — which describes the package's plumbing rather than the failure. The five structural kinds skipped are errors.message, errors.stack, errors.hint, errors.detail and errors.attrs.

errors.join is not skipped: an aggregate reports errors.join as its identity.

This is what belongs in a log record's kind field and in OTEL's exception.type.

StackOf

func StackOf(err error) StackTrace

Returns the outermost stack in the tree, or nil. Outermost is the most recently captured, and so the closest to where the error surfaced.

errors.StackOf(ErrNotFound)                      // nil — a sentinel carries none
errors.StackOf(errors.WithStack(ErrNotFound))    // the stack from the WithStack call

Render it with StackTrace.String().

Is, As, AsType and Unwrap

func Is(err, target error) bool
func As(err error, target any) bool
func AsType[E error](err error) (E, bool)
func Unwrap(err error) error

Straight passthroughs to the standard library's errors package, so this package can be a drop-in import without a second import for the basics.

Prefer AsType over As — it needs no out-parameter:

if e, ok := errors.AsType[*MyErr](err); ok {
    // e is a *MyErr
}