5. Error Handling
Design clear error contracts; wrap with context; make failures actionable and diagnosable.
Question: What is the difference between a sentinel error and a typed error? When should you use each?
Answer:
Sentinel Error: A specific, exported error value, like
io.EOF
orsql.ErrNoRows
. They are compared by value (errors.Is(err, sql.ErrNoRows)
).Typed Error: A custom error
struct
that implements theerror
interface. They are checked by type (errors.As(err, &myErr)
), allowing you to inspect their fields for more context.
Explanation: Use sentinel errors for simple, fixed error conditions. Prefer typed errors when you need to provide more context about the failure, such as a status code, a timeout flag, or other metadata that the caller can programmatically inspect. Avoid exporting too many sentinel errors from a package, as it can bloat the API.
// Example: typed error with errors.Is
var ErrNotFound = errors.New("not found")
type TemporaryError struct{ Err error }
func (e TemporaryError) Error() string { return e.Err.Error() }
func (e TemporaryError) Unwrap() error { return e.Err }
func find(id string) (Item, error) {
// ...
return Item{}, fmt.Errorf("find %s: %w", id, ErrNotFound)
}
func handler(w http.ResponseWriter, r *http.Request) {
_, err := find("42")
if err != nil {
switch {
case errors.Is(err, ErrNotFound):
http.Error(w, "not found", http.StatusNotFound)
case errors.As(err, new(TemporaryError)):
http.Error(w, "try again", http.StatusServiceUnavailable)
default:
http.Error(w, "internal", http.StatusInternalServerError)
}
return
}
}
Question: How should you wrap and propagate errors?
Answer: Wrap errors with context using %w
and inspect with errors.Is/As
. Return errors upward; avoid logging and returning the same error at multiple layers.
Explanation: Double-logging creates noisy logs. Log at the boundary (e.g., HTTP handler, CLI) with sufficient context and map errors to user-facing messages or status codes there.
Question: What is
errors.Join
and when is it useful?
Answer: errors.Join
combines multiple errors into one. errors.Is/As
work across the joined set.
Explanation: Useful when multiple cleanup steps can fail or when aggregating parallel errors. Preserve the original errors to avoid losing diagnostics.
Question: How do
context.WithCancelCause
andcontext.Cause
help error propagation?
Answer: They attach a concrete cause to cancellation so callers can log/report the underlying reason instead of generic context canceled
.
Explanation: Distinguish timeouts (DeadlineExceeded
) from domain cancellations.