Constructors¶
The seven functions that make an error. Behaviour below was taken from the package source and confirmed by running it.
New¶
Returns a leaf error carrying msg and a stack captured at the call site.
KindOf on it reports errors.basic.
Not for a package-level sentinel
At package scope the call site is package initialisation, so the stack
points at runtime.doInit and the var line rather than anywhere the error
was returned from. Use NewSentinel.
Newf and Errorf¶
The same function under two names — both spellings are in use across the estate. Formats the message and captures a stack.
%w is honoured, including more than once, because the message is built with
fmt.Errorf rather than a reimplementation of it.
Note that the leaf produced this way has no kind of its own: KindOf returns
"" for an error whose innermost layer came from Newf, and a slog record of
it therefore carries no kind field. Give the leaf a
NewSentinel identity if you need one.
NewSentinel¶
Returns a package-level sentinel: no stack trace, and a stable kind string.
The absence of a stack is the point — see the warning on New. Add a
real one where you return it:
The kind is a serialisation key
Once a kind has crossed a process boundary, changing it breaks decoders that
have not been updated. Treat it as you would a protobuf field number, and
namespace it — forge.not_found, not not_found.
Wrap and Wrapf¶
Prefixes err's message and captures a stack at the call site. The rendered
message is msg + ": " + err.Error().
Both return nil when err is nil, so they need no guard:
Is and As continue to match through the wrapper, so wrapping a sentinel does
not hide it:
Join¶
Aggregates its non-nil arguments. Returns nil when none remain, so
Join(nil, nil) and Join() are both nil.
Error() renders one member per line:
No stack is attached to the aggregate. The joined errors carry their own, and adding one at this level is precisely what would break the shape described below.
Why this one behaves¶
Join satisfies Unwrap() []error directly — the aggregate is the outermost
type, not something wrapped around it.
That matters because it is where cockroachdb/errors goes wrong: its Join
wraps the aggregate in a stack-carrying layer, so the outermost type offers only
a single unwrap, and every reporting traversal in that library then treats the
aggregate as a leaf. Hints, details, context tags and telemetry keys all vanish
below a Join (their issue #162, and it bites on the one-error path too, since
their Join wraps even a single error).
Here, annotations below an aggregate stay readable:
err := errors.Join(
errors.WithHint(errors.New("first"), "fix the first"),
errors.WithAttrs(errors.New("second"), slog.Int("code", 42)),
)
errors.Hints(err) // ["fix the first"]
errors.Attrs(err) // [code=42]
KindOf on an aggregate reports errors.join, since that is the outermost kind
that is not one of this package's annotation wrappers.