V
Vel·ToolKit
Simple · Fast · Ready to use
EN
Chapter 13 of 20

Error Handling

The error interface, errors.Is/As, panic/recover

The error Interface

Go doesn't use exceptions; a function returns errors explicitly as its last return value. error is an interface with a single Error() string method.

package main

import (
    "fmt"
    "os"
)

func readSize(path string) (int64, error) {
    f, err := os.Open(path)
    if err != nil {
        return 0, fmt.Errorf("open %s: %w", path, err) // wrap with %w
    }
    defer f.Close()

    info, err := f.Stat()
    if err != nil {
        return 0, err
    }
    return info.Size(), nil
}

func main() {
    n, err := readSize("/etc/hostname")
    fmt.Println(n, err)
}

errors.Is and errors.As

The standard errors package provides matching tools that see through multiple layers of %w wrapping.

package main

import (
    "errors"
    "fmt"
    "io/fs"
    "os"
)

func main() {
    _, err := os.Open("/no/such/file")

    if errors.Is(err, fs.ErrNotExist) {
        fmt.Println("file missing")
    }

    var pe *fs.PathError
    if errors.As(err, &pe) {
        fmt.Println("path:", pe.Path, "op:", pe.Op)
    }
}

Custom Error Types

package main

import "fmt"

type ValidationError struct {
    Field, Reason string
}

func (e *ValidationError) Error() string {
    return e.Field + ": " + e.Reason
}

func validate(name string) error {
    if name == "" {
        return &ValidationError{Field: "name", Reason: "empty"}
    }
    return nil
}

func main() {
    fmt.Println(validate(""))
}

panic / recover

panic is a real "exception" that unwinds the call stack. Don't use it unless you genuinely cannot continue. recover only catches a panic when called inside a defer.

package main

import "fmt"

func risky() {
    panic("boom")
}

func safe() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    risky()
    return
}

func main() {
    fmt.Println(safe())
}