Go: Sentinel errors and errors.Is() slow your code down by 3000%

Performance of errors.Is in Go

  • Benchmarks showed errors.Is significantly slower than a boolean check; later correction reduced the claim from “3000% / 30x” to roughly “500% / 5x” for the microbenchmark.
  • Several commenters stress the absolute times are tiny (single‑digit tens of nanoseconds) and usually dwarfed by I/O, but acknowledge it can matter in very hot in‑memory loops.
  • Key costs:
    • Pre‑fix: no fast path for err == nil, so even the happy path paid reflection and tree‑walk overhead.
    • Implementation uses reflection and walks wrapped error chains.
    • Inlining behavior matters; when functions were inlined, some benchmark variants were unrealistically optimized away.

Go design & upcoming improvements

  • Newer (unreleased) Go adds a fast check for err == nil || target == nil in errors.Is, expected to reduce overhead on the happy path.
  • Some discuss possible further optimizations (splitting reflection out to allow better inlining), and an ongoing inliner improvement effort.

Sentinel errors vs booleans (ok pattern)

  • Debate over whether “not found” should be a sentinel error or a bool like Go map lookups.
  • Arguments for bool:
    • Faster in tight loops.
    • “Not found” is often a normal outcome, not truly exceptional.
  • Arguments for errors:
    • Nice symmetry with other failure modes.
    • Allows including which value/key was missing.
    • Sentinel errors already used in stdlib (e.g., sql.ErrNoRows, io.EOF), though usually in slow operations.

Error construction vs inspection

  • Multiple comments note most time is spent constructing errors (formatting strings, allocations, multiple wraps), not in errors.Is itself.
  • Deeply wrapped errors (many %w layers) are especially expensive and often produce unreadable messages.
  • Some advocate wrapping on every layer for debuggability and structured logs, then only optimizing hot spots once profiling shows a problem.
  • Others argue for treating errors as mostly opaque, avoiding pervasive wrapping and using stack traces and structured logging instead.

Broader language comparisons & philosophy

  • Rust: discussion about exposing dependency error types, #[non_exhaustive], and desire for exhaustive matching vs semver stability.
  • Java/C#/C++/JS: long side thread on “error vs exception” semantics and checked vs unchecked behavior.
  • Some see Go’s error handling and if err != nil patterns (plus variable shadowing with ok/err) as awkward; others consider it a reasonable trade‑off for simplicity and clarity.
  • Several emphasize profiling and avoiding premature optimization; errors.Is is rarely the dominant cost in real systems.