Master Go's error handling philosophy: errors as values, custom error types, errors.Is/As, wrapping, sentinel errors, and panic/recover.
Time
30 minutes
Prerequisites
Lab 05 (Interfaces)
Tools
Docker image: zchencow/innozverse-go:latest
Lab Instructions
Step 1: The error Interface
dockerrun--rmzchencow/innozverse-go:latestgorun-<<'EOF'package mainimport ( "errors" "fmt")// errors.New — simple errorvar ErrDivByZero = errors.New("division by zero")// fmt.Errorf — formatted errorfunc safeDivide(a, b float64) (float64, error) { if b == 0 { return 0, ErrDivByZero } return a / b, nil}func parseAge(s string) (int, error) { var age int if _, err := fmt.Sscanf(s, "%d", &age); err != nil { return 0, fmt.Errorf("parseAge(%q): %w", s, err) // %w wraps the error } if age < 0 || age > 150 { return 0, fmt.Errorf("parseAge(%q): age %d out of range [0, 150]", s, age) } return age, nil}func main() { // Always check errors result, err := safeDivide(10, 3) if err != nil { fmt.Println("Error:", err) } else { fmt.Printf("10/3 = %.4f\n", result) } _, err = safeDivide(5, 0) // errors.Is — checks if error IS (or wraps) a specific error fmt.Println("Is ErrDivByZero:", errors.Is(err, ErrDivByZero)) // Error wrapping chain age, err := parseAge("25") fmt.Printf("Age: %d, err: %v\n", age, err) _, err = parseAge("abc") fmt.Println("Error:", err) _, err = parseAge("200") fmt.Println("Error:", err)}EOF
💡 %w in fmt.Errorf wraps an error, maintaining the error chain. errors.Is(err, target) walks the entire chain — if any error in the chain matches target, it returns true. Without %w (using %v or %s instead), the original error is lost and errors.Is won't find it.
📸 Verified Output:
Step 2: Custom Error Types
💡 errors.As(err, &target) is the typed version of errors.Is. It finds the first error in the chain that is assignable to target and sets target to it. Use it to extract typed error details. Go 1.20+ supports errors.Join and returning multiple errors from a single call.
10/3 = 3.3333
Is ErrDivByZero: true
Age: 25, err: <nil>
Error: parseAge("abc"): expected integer
Error: parseAge("200"): age 200 out of range [0, 150]
docker run --rm zchencow/innozverse-go:latest go run - << 'EOF'
package main
import (
"errors"
"fmt"
)
// Custom error type with fields
type ValidationError struct {
Field string
Value any
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s=%v: %s", e.Field, e.Value, e.Message)
}
type NotFoundError struct {
Resource string
ID any
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with id=%v not found", e.Resource, e.ID)
}
// Multi-error type
type MultiError struct{ Errs []error }
func (m *MultiError) Error() string {
msgs := make([]string, len(m.Errs))
for i, e := range m.Errs { msgs[i] = e.Error() }
result := ""
for i, m := range msgs {
if i > 0 { result += "; " }
result += m
}
return fmt.Sprintf("%d error(s): %s", len(msgs), result)
}
func (m *MultiError) Unwrap() []error { return m.Errs }
func validateProduct(name string, price float64, stock int) error {
var errs []error
if name == "" {
errs = append(errs, &ValidationError{"name", name, "cannot be empty"})
}
if price <= 0 {
errs = append(errs, &ValidationError{"price", price, "must be positive"})
}
if stock < 0 {
errs = append(errs, &ValidationError{"stock", stock, "cannot be negative"})
}
if len(errs) > 0 { return &MultiError{errs} }
return nil
}
func findProduct(id int) (string, error) {
products := map[int]string{1: "Surface Pro", 2: "Surface Pen"}
if p, ok := products[id]; ok { return p, nil }
return "", &NotFoundError{"product", id}
}
func main() {
// errors.As — unwraps and checks type
_, err := findProduct(99)
var notFound *NotFoundError
if errors.As(err, ¬Found) {
fmt.Printf("Resource=%s ID=%v\n", notFound.Resource, notFound.ID)
}
// Multi-error
err = validateProduct("", -10, -5)
if err != nil {
fmt.Println("Validation:", err)
var me *MultiError
if errors.As(err, &me) {
fmt.Printf(" → %d sub-errors\n", len(me.Errs))
for _, e := range me.Errs {
var ve *ValidationError
if errors.As(e, &ve) {
fmt.Printf(" field=%s: %s\n", ve.Field, ve.Message)
}
}
}
}
err = validateProduct("Surface Pro", 864, 15)
fmt.Println("Valid product:", err)
}
EOF
Resource=product ID=99
Validation: 3 error(s): validation failed for name=: cannot be empty; validation failed for price=-10: must be positive; validation failed for stock=-5: cannot be negative
→ 3 sub-errors
field=name: cannot be empty
field=price: must be positive
field=stock: cannot be negative
Valid product: <nil>
Recovered: panic: something went wrong
Normal run
Normal err: <nil>
AppError: [404] product not found: not found
Is ErrNotFound: true
r1: 42
r2 or default: -1
[retry] waiting 1ms before attempt 2
[retry] waiting 2ms before attempt 3
Retry result: success after retries
[error] request="notfound" error=[404] resource not found: not found
[error] request="panic" error=recovered from panic: unexpected crash
[error] request="bad" error=[400] bad request: invalid input
[ok] → 200 OK