How to Return and Check Errors in Go

In Go, functions return errors as a second (or final) return value, and you must explicitly check them using `if err != nil` before proceeding.

In Go, functions return errors as a second (or final) return value, and you must explicitly check them using if err != nil before proceeding. Ignoring an error is a compile-time error unless you intentionally assign it to the blank identifier _.

Here is the standard pattern for defining a function that returns an error and the caller handling it:

// Define a function that returns a value and an error
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := Divide(10, 0)
    
    // Always check the error immediately
    if err != nil {
        // Handle the error: log, return, or panic depending on context
        fmt.Printf("Error occurred: %v\n", err)
        return // Stop execution if the error is fatal
    }

    // Safe to use result only if err is nil
    fmt.Printf("Result: %v\n", result)
}

For I/O operations or library calls, you often chain error checks. If you need to wrap an error to add context (Go 1.13+), use fmt.Errorf with the %w verb:

func ReadConfig(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        // Wrap the original error with context
        return fmt.Errorf("failed to read config file %s: %w", path, err)
    }
    
    // Process data...
    return nil
}

Key practices to remember:

  1. Check immediately: Do not defer error checking until the end of a function; handle it right after the call to prevent using invalid data.
  2. Return early: If an error occurs, return immediately from the function to avoid deep nesting of if/else blocks.
  3. Use errors.Is and errors.As: When checking for specific error types or sentinel errors, use these standard library functions instead of comparing error strings or types directly.
if errors.Is(err, os.ErrNotExist) {
    // Handle specific "file not found" case
}

This explicit error handling model forces developers to acknowledge failure cases, making Go applications more robust and predictable compared to languages that rely on exceptions.