Go Standard Error Patterns

Browse canonical Go errors from the standard library with package and sentinel values.

A searchable reference for Go standard-library sentinel errors (io.EOF, sql.ErrNoRows, context.Canceled), concrete error types, and the errors.Is / errors.As / %w wrapping conventions. Runs in your browser. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

Why compare with errors.Is instead of == in Go?

Wrapping an error with fmt.Errorf and the %w verb nests the original inside a chain, so a direct == comparison against a sentinel like io.EOF or sql.ErrNoRows fails. errors.Is walks the whole chain and returns true if any wrapped error equals the target.

This is a searchable reference for Go’s standard error patterns — the canonical sentinel errors exported by the standard library (io.EOF, sql.ErrNoRows, context.Canceled), the concrete error types you inspect with errors.As (*os.PathError, *net.DNSError, *json.SyntaxError), and the errors/fmt helpers that tie them together.

How it works

Go errors are just values implementing the error interface, and the standard library distinguishes three families. Sentinel errors are exported package variables (io.EOF, fs.ErrNotExist, context.DeadlineExceeded) that you test against with errors.Is. Error types are concrete structs (*os.PathError, *strconv.NumError) carrying extra fields you extract with errors.As. Helperserrors.New, fmt.Errorf with %w, errors.Is, errors.As, errors.Join — create and unwrap these.

The critical rule is that wrapping with fmt.Errorf("read config: %w", err) nests the original error in a chain. A bare err == io.EOF will then fail, while errors.Is(err, io.EOF) walks the chain and still matches. The same applies to types: errors.As(err, &pathErr) finds a *os.PathError no matter how deeply it is wrapped.

The three families in detail

Sentinel errors

Sentinels are package-level variables you compare using errors.Is. The most common ones across packages include:

  • io.EOF — clean end of a reader; not a failure in a normal read loop
  • io.ErrUnexpectedEOF — the stream ended mid-record; this one is a real error
  • fs.ErrNotExist — file not found (also returned as the Err field inside a *os.PathError)
  • fs.ErrPermission — permission denied
  • context.Canceled — the caller cancelled the context
  • context.DeadlineExceeded — a timeout fired
  • sql.ErrNoRows — a QueryRow().Scan found nothing; this is expected, not a fault

Concrete error types

Types carry richer information accessible after an errors.As extraction:

  • *os.PathError — wraps file-system errors, with .Op (the operation name) and .Path (the affected path)
  • *net.DNSError — DNS lookup failures, with .Name, .IsTimeout, .IsNotFound
  • *json.SyntaxError — JSON parse problems, with .Offset pointing to the bad byte
  • *strconv.NumError — parse failures from Atoi, ParseFloat, with .Func and .Num

Error helpers

  • errors.New(text) — creates an opaque sentinel; use for your own package-level errors
  • fmt.Errorf("context: %w", err) — wraps an error while keeping the cause reachable
  • errors.Is(err, target) — walks the chain looking for a matching sentinel
  • errors.As(err, &ptr) — walks the chain looking for a matching concrete type
  • errors.Join(e1, e2, ...) — combines multiple errors into one; each remains unwrappable via errors.Is/errors.As

Example

Wrap once, then inspect through the chain:

data, err := io.ReadAll(r)
if err != nil {
    err = fmt.Errorf("read body: %w", err)   // wrap, keep the cause
}

if errors.Is(err, io.ErrUnexpectedEOF) {     // sentinel via chain
    // truncated input
}

var pathErr *os.PathError
if errors.As(err, &pathErr) {                // concrete type via chain
    log.Printf("op=%s path=%s", pathErr.Op, pathErr.Path)
}

Common mistakes

Using == instead of errors.Is: Once an error is wrapped, err == io.EOF silently returns false even when EOF is the root cause. Always prefer errors.Is.

Treating sql.ErrNoRows as a fault: QueryRow().Scan returns sql.ErrNoRows when a query finds no matching row. This is normal application logic, not a database error — handle it with a dedicated branch and do not log it as an error.

Swallowing io.ErrUnexpectedEOF: Unlike io.EOF, this one means the connection or file ended before a complete record arrived. It should surface as a real error to the caller.

Note that everything runs in your browser; nothing is uploaded.