Errors in Go
Errors are values. Handle them explicitly and return them up the call stack with context.
Basics
error is an interface type with a single Error() string method. Functions return (T, error) when failures are possible. The zero value for error is nil (no error).
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
v, err := divide(10, 2)
if err != nil { fmt.Println("err:", err); return }
fmt.Println(v)
}
Wrapping with context
Wrap errors with %w using fmt.Errorf to preserve the original cause while adding context. This enables unwrapping with errors.Is/As.
package main
import (
"errors"
"fmt"
"strconv"
)
func parseInt(s string) (int, error) {
v, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parse %q: %w", s, err) // wrap
}
return v, nil
}
func main() {
if _, err := parseInt("x"); err != nil {
fmt.Println("failed:", err)
}
}
Unwrapping: Is and As
Use errors.Is to test against sentinels and errors.As to extract a specific error type from a wrapped chain.
package main
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) error {
_, err := os.Open(path)
if err != nil {
return fmt.Errorf("open config: %w", err)
}
return nil
}
func main() {
err := readConfig("no-such-file")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("missing file")
}
}
Temporary vs permanent errors
Model retryable conditions with typed errors or wrappers; back off and limit retries.
type Temporary interface{ Temporary() bool }
Custom error types
package main
import "fmt"
type ValidationError struct {
Field, Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
func validateEmail(email string) error {
if email == "" {
return ValidationError{Field: "email", Message: "required"}
}
return nil
}
Sentinel errors
package main
import "errors"
var ErrTimeout = errors.New("timeout")
Use sentinels sparingly; prefer typed errors or context-rich wrapping.
Best practices
- Always check
err; return early - Add context with
fmt.Errorf("...: %w", err) - Prefer typed errors for structured handling; use
errors.Is/errors.As - Avoid panics in normal flow; use panics only for truly exceptional programmer errors
- Log at the boundary; return rich errors within libraries
- Translate external errors (DB, HTTP) into domain errors at your package boundary
Summary
- Errors are values; handle explicitly
- Wrap with
%wand inspect withIs/As - Use custom/typed errors for clarity and better handling