Core Concepts

Error Handling

Handle errors the Go way — explicit, straightforward, and without exceptions.

Go's Error Philosophy

Go doesn't use exceptions. Errors are just values — functions return (result, error) and callers handle them.

This makes error handling explicit and visible in the code.

The error Interface

The built-in error interface has one method: Error() string.

Custom Errors

Create custom error types for richer error information using errors.New, fmt.Errorf, or custom structs.

Wrapping Errors

Use fmt.Errorf with %w to wrap errors (Go 1.13+). Unwrap with errors.Is and errors.As.

defer, panic, recover

  • defer — runs when function exits (cleanup)
  • panic — stops normal execution
  • recover — catches a panic (like try/catch, use rarely)

Example

go
package main

import (
    "errors"
    "fmt"
)

// Sentinel errors
var ErrNotFound = errors.New("not found")
var ErrPermission = errors.New("permission denied")

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

// Function returning error
func getUser(id int) (string, error) {
    if id <= 0 {
        return "", &ValidationError{Field: "id", Message: "must be positive"}
    }
    if id > 100 {
        return "", fmt.Errorf("getUser: %w", ErrNotFound)
    }
    return fmt.Sprintf("user_%d", id), nil
}

// defer for cleanup
func processFile(path string) error {
    fmt.Println("Opening:", path)
    defer fmt.Println("Closing:", path)  // always runs

    // ... process file
    return nil
}

func main() {
    // Basic error handling
    user, err := getUser(42)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Got user:", user)

    // Check specific error type
    _, err = getUser(-1)
    var validErr *ValidationError
    if errors.As(err, &validErr) {
        fmt.Printf("Invalid field '%s': %s\n", validErr.Field, validErr.Message)
    }

    // Check sentinel error
    _, err = getUser(200)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("User not found!")
    }

    // defer
    processFile("data.txt")

    // panic/recover
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    // panic("something went wrong")  // would be caught
}
Try it yourself — GO