SyntaxStudy
Sign Up
Go Idiomatic Error Handling Patterns
Go Beginner 1 min read

Idiomatic Error Handling Patterns

Writing robust Go code requires consistent and thoughtful error handling. One principle is to add context when propagating errors: rather than returning a bare error, wrap it with information about the operation that failed (`fmt.Errorf("open config %s: %w", path, err)`). This creates an error chain that reads like a stack trace in plain English. The "errors as values" approach enables sophisticated patterns. An errWriter pattern accumulates the first error encountered during a series of write operations, so that individual Write calls do not need to check for errors individually. This reduces boilerplate while preserving correct behaviour. Similar patterns are used in `database/sql` for transaction management. For concurrent code, errors must be communicated back to the caller via channels or synchronisation structures. A common pattern returns errors through a dedicated error channel or uses a `sync.WaitGroup` combined with a shared `error` variable protected by a mutex. The `golang.org/x/sync/errgroup` package provides a higher-level abstraction for running goroutines and collecting their errors.
Example
package main

import (
    "errors"
    "fmt"
    "io"
    "strings"
)

// errWriter accumulates the first write error.
type errWriter struct {
    w   io.Writer
    err error
}

func (ew *errWriter) write(s string) {
    if ew.err != nil {
        return
    }
    _, ew.err = fmt.Fprint(ew.w, s)
}

// Inline error handling in a pipeline
func buildMessage(name, greeting string) (string, error) {
    if name == "" {
        return "", errors.New("name must not be empty")
    }
    if greeting == "" {
        return "", errors.New("greeting must not be empty")
    }
    return greeting + ", " + name + "!", nil
}

// Collecting errors from goroutines via channel
func runTasks(tasks []func() error) []error {
    errs := make(chan error, len(tasks))
    for _, t := range tasks {
        t := t
        go func() { errs <- t() }()
    }
    var result []error
    for range tasks {
        if err := <-errs; err != nil {
            result = append(result, err)
        }
    }
    return result
}

func main() {
    var sb strings.Builder
    ew := &errWriter{w: &sb}
    ew.write("Hello")
    ew.write(", ")
    ew.write("World")
    ew.write("!")
    if ew.err != nil {
        fmt.Println("write error:", ew.err)
    } else {
        fmt.Println(sb.String())
    }

    msg, err := buildMessage("Go", "Hello")
    if err != nil { fmt.Println(err) } else { fmt.Println(msg) }

    tasks := []func() error{
        func() error { return nil },
        func() error { return errors.New("task 2 failed") },
        func() error { return nil },
        func() error { return errors.New("task 4 failed") },
    }
    for _, e := range runTasks(tasks) {
        fmt.Println("task error:", e)
    }
}