Error Handling
Go has no exceptions. Functions return (value, error) and you handle errors at the call site. With errors.Is, errors.As, and wrapping via %w, the model becomes powerful for real apps.
Idiomatic error handling
EXAMPLE
package main
import (
"errors"
"fmt"
"io"
"net/http"
)
// 1) Sentinel errors — values you can compare with errors.Is
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
)
// 2) Wrapped errors — preserve cause, add context
func loadUser(id string) (User, error) {
body, err := fetch("/users/" + id)
if err != nil {
return User{}, fmt.Errorf("loadUser(%s): %w", id, err)
}
var u User
if err := json.Unmarshal(body, &u); err != nil {
return User{}, fmt.Errorf("loadUser(%s) decode: %w", id, err)
}
return u, nil
}
// 3) Inspect at the boundary with errors.Is / errors.As
u, err := loadUser(id)
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "unknown user", 404); return
}
if errors.Is(err, io.EOF) {
// remote closed — retry
}
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
log.Warn("dns: " + dnsErr.Name)
}
log.Error(err)
http.Error(w, "internal", 500); return
}
// 4) Custom error types — carry data
type ValidationError struct {
Field, Reason string
}
func (v *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", v.Field, v.Reason)
}
func parseAge(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil { return 0, &ValidationError{"age", "not a number"} }
if n < 0 { return 0, &ValidationError{"age", "must be >= 0"} }
return n, nil
}
var v *ValidationError
if errors.As(err, &v) {
return Problem{Field: v.Field, Detail: v.Reason}
}
// 5) Don't ignore errors
if _, err := os.Open("x"); err != nil {
return err // good
}
_, _ = io.Copy(dst, src) // bad — silently drops everything
// 6) panic / recover — only at process boundaries (servers, goroutine entry)
func safeHandler(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Error("panic: ", r, debug.Stack())
http.Error(w, "internal", 500)
}
}()
h(w, r)
}
}
Why it matters
fmt.Errorf(\"...: %w\", err) wraps the cause, preserving errors.Is/As while adding a breadcrumb. Wrap at each layer; inspect once at the boundary.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
f, err := os.Open("data.txt")
if err != nil {
return fmt.Errorf("open: %w", err)
}
defer f.Close()
Try it Yourself »
Exercise
Idiomatic error check.
if err
nil { return err }
Two characters.
Discussion
Loading…