ZetCode

Golang error type

last modified May 8, 2025

This tutorial explains how to use the error built-in type in Go. We'll cover error handling basics with practical examples of proper error management in Go programs.

The error type is a built-in interface in Go used for error handling. It's a fundamental part of Go's approach to explicit error checking rather than exceptions. The error interface requires a single method: Error() string.

In Go, functions that might fail typically return an error as their last return value. The convention is to check this error value immediately after the function call. This makes error handling explicit and predictable.

Basic error handling example

The simplest error handling checks if a function returned an error. This example demonstrates basic error checking in Go.
Note: Always handle errors immediately after the function call.

basic_error.go
package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

func main() {

    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

The divide function returns an error when attempting division by zero. The caller checks the error before proceeding with the result.

Creating custom error types

We can create custom error types by implementing the error interface. This example shows how to define and use a custom error type.

custom_error.go
package main

import (
    "fmt"
    "time"
)

type TimeoutError struct {
    Operation string
    Timeout   time.Duration
}

func (e *TimeoutError) Error() string {
    return fmt.Sprintf("%s timed out after %v", e.Operation, e.Timeout)
}

func process() error {
    return &TimeoutError{
        Operation: "database query",
        Timeout:   5 * time.Second,
    }
}

func main() {

    err := process()
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Operation successful")
}

The TimeoutError type implements the error interface with additional context about the operation. Custom errors provide more detailed error information.

Error wrapping with fmt.Errorf

Go 1.13 introduced error wrapping with fmt.Errorf and the %w verb. This example shows how to wrap errors to preserve context.

error_wrapping.go
package main

import (
    "errors"
    "fmt"
    "os"
)

func readConfig() error {
    _, err := os.ReadFile("config.json")
    if err != nil {
        return fmt.Errorf("read config: %w", err)
    }
    return nil
}

func main() {

    err := readConfig()
    if err != nil {
        fmt.Println("Error:", err)
        
        var pathErr *os.PathError
        if errors.As(err, &pathErr) {
            fmt.Println("Failed to access file:", pathErr.Path)
        }
    }
}

The %w verb wraps the original error while adding context. The errors.As function checks for specific error types in the chain.

Comparing errors with errors.Is

The errors.Is function checks for specific errors in an error chain. This example demonstrates error comparison with sentinel errors.

error_comparison.go
package main

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

var ErrFileNotFound = errors.New("file not found")

func openFile(name string) error {
    _, err := os.Open(name)
    if err != nil {
        if errors.Is(err, os.ErrNotExist) {
            return ErrFileNotFound
        }
        return fmt.Errorf("open file: %w", err)
    }
    return nil
}

func main() {

    err := openFile("missing.txt")
    if errors.Is(err, ErrFileNotFound) {
        fmt.Println("Custom file not found error")
    }
    if err != nil {
        fmt.Println("Error:", err)
    }
}

The example defines a sentinel error ErrFileNotFound. The errors.Is function checks for this specific error in the chain.

Panic and recover for unexpected errors

While most errors should be handled normally, panic and recover handle truly exceptional situations. This example shows proper panic/recover usage.

panic_recover.go
package main

import (
    "fmt"
    "log"
)

func processTask(taskID int) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Recovered from panic in task %d: %v", taskID, r)
        }
    }()
    
    if taskID == 0 {
        panic("invalid task ID")
    }
    fmt.Printf("Processing task %d\n", taskID)
}

func main() {

    processTask(1)
    processTask(0) // This will panic
    processTask(2)
    fmt.Println("All tasks processed")
}

The deferred recover function handles panics gracefully. This pattern is useful for server applications that must continue running after errors.

Source

Go language specification

This tutorial covered the error type in Go with practical examples of error handling, custom errors, wrapping, comparison, and recovery.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Golang tutorials.