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. Helpers — errors.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.
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)
}
Note that sql.ErrNoRows from QueryRow().Scan is a normal result meaning “not found”, not a failure — handle it explicitly with errors.Is rather than treating every non-nil error the same way. Everything runs in your browser; nothing is uploaded.