Skip to content

Go Error Handling

In Go, errors are values. Instead of using "exceptions" (like try/catch in Java or Python), Go functions return an error as their last return value.

Basic Error Handling

The error type is a built-in interface. If a function succeeds, it returns nil for the error.

package main

import (
    "errors"
    "fmt"
)

func f(arg int) (int, error) {
    if arg == 42 {
        // Return a simple error using errors.New
        return -1, errors.New("can't work with 42")
    }
    return arg + 3, nil
}

func main() {
    result, err := f(42)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

Custom Errors

You can create your own error types by implementing the Error() method. This is useful for passing extra data back to the caller.

package main

import "fmt"

type argError struct {
    arg  int
    prob string
}

func (e *argError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.prob)
}

func f(arg int) (int, error) {
    if arg == 42 {
        return -1, &argError{arg, "can't work with it"}
    }
    return arg + 3, nil
}

func main() {
    _, err := f(42)
    if ae, ok := err.(*argError); ok {
        fmt.Println(ae.arg)  // Access custom fields
        fmt.Println(ae.prob)
    }
}

Error Wrapping (%w)

In modern Go (1.13+), you can "wrap" an error to add more context while keeping the original error visible.

1
2
3
4
5
6
7
8
9
func doSomething() error {
    err := originalError()
    return fmt.Errorf("extra context: %w", err)
}

// To check for a specific error in a chain:
if errors.Is(err, ErrNotFound) {
    // ...
}

Why handle errors explicitly?

  1. Safety: You are forced to think about what happens when things go wrong.
  2. No Surprises: Control flow is clear; there are no hidden jumps (exceptions).
  3. Better Debugging: Error chains give you a clear "trail" of what failed and why.