← Back to Curriculum

Error Handling

šŸ“š Lesson 8 of 16 ā±ļø 35 min

Error Handling

35 min

Go uses explicit error handling with error return values, making error handling visible and mandatory. The idiomatic pattern is `value, err := function()`, where errors are returned as the last value. Go doesn't have exceptions—errors are handled explicitly. This makes error handling predictable and forces developers to consider error cases. Understanding Go's error handling is essential for writing robust code.

Errors in Go are values that implement the `error` interface, which requires a `Error() string` method. Errors are first-class values that can be passed around, compared, and wrapped. The standard library provides `errors.New()` and `fmt.Errorf()` for creating errors. Understanding errors as values helps you handle them effectively.

Custom errors can be created using `errors.New()` for simple errors or `fmt.Errorf()` for formatted errors. Custom error types can be created by implementing the `error` interface. Error wrapping (Go 1.13+) allows adding context to errors using `fmt.Errorf()` with `%w`. Understanding custom errors helps you provide meaningful error messages.

The `defer` statement ensures cleanup code executes even when errors occur or functions panic. Defer is commonly used with error handling to close files, unlock mutexes, or clean up resources. Deferred calls execute in LIFO order when the function returns. Understanding defer helps you write robust code that properly manages resources.

Error handling patterns include checking errors immediately, returning errors up the call stack, wrapping errors with context, and using `defer` for cleanup. The `if err != nil { return err }` pattern is idiomatic. Understanding error handling patterns helps you write consistent, maintainable code.

Best practices include always checking errors, providing context when returning errors, using `defer` for cleanup, creating custom error types for important errors, and not ignoring errors. Error handling should be explicit and visible. Understanding error handling enables you to write robust, production-ready Go code.

Key Concepts

  • Go uses explicit error handling with error return values.
  • Errors are values that implement the error interface.
  • Custom errors created with errors.New() or fmt.Errorf().
  • defer ensures cleanup even when errors occur.
  • Error wrapping (Go 1.13+) adds context to errors.

Learning Objectives

Master

  • Handling errors explicitly with error return values
  • Creating custom errors and error types
  • Using defer for resource cleanup
  • Wrapping errors with context

Develop

  • Error handling strategy thinking
  • Understanding Go's error philosophy
  • Writing robust, production-ready code

Tips

  • Always check errors—don't ignore them.
  • Use defer for cleanup (closing files, unlocking mutexes).
  • Wrap errors with context using fmt.Errorf() with %w.
  • Return errors up the call stack—don't handle them silently.

Common Pitfalls

  • Ignoring errors, causing silent failures.
  • Not using defer for cleanup, causing resource leaks.
  • Not providing context when returning errors, making debugging hard.
  • Panicking instead of returning errors for expected error cases.

Summary

  • Go uses explicit error handling with error return values.
  • Errors are values that implement the error interface.
  • Custom errors can be created and wrapped with context.
  • defer ensures cleanup executes even when errors occur.
  • Understanding error handling is essential for robust Go code.

Exercise

Demonstrate error handling patterns and custom error creation.

package main

import (
    "errors"
    "fmt"
    "strconv"
)

// Custom error type
type ValidationError struct {
    Field string
    Value string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("validation failed for field '%s' with value '%s'", e.Field, e.Value)
}

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

// Function with custom error
func validateAge(age string) (int, error) {
    ageInt, err := strconv.Atoi(age)
    if err != nil {
        return 0, fmt.Errorf("invalid age format: %w", err)
    }
    
    if ageInt < 0 {
        return 0, &ValidationError{Field: "age", Value: age}
    }
    
    if ageInt > 150 {
        return 0, &ValidationError{Field: "age", Value: age}
    }
    
    return ageInt, nil
}

// Function with defer for cleanup
func processFile(filename string) error {
    fmt.Printf("Opening file: %s
", filename)
    defer fmt.Printf("Closing file: %s
", filename)
    
    // Simulate file processing
    if filename == "" {
        return errors.New("filename cannot be empty")
    }
    
    fmt.Printf("Processing file: %s
", filename)
    return nil
}

func main() {
    // Basic error handling
    result, err := divide(10, 2)
    if err != nil {
        fmt.Printf("Error: %v
", err)
        return
    }
    fmt.Printf("Result: %.2f
", result)
    
    // Error with division by zero
    _, err = divide(10, 0)
    if err != nil {
        fmt.Printf("Error: %v
", err)
    }
    
    // Custom error handling
    age, err := validateAge("25")
    if err != nil {
        fmt.Printf("Validation error: %v
", err)
        return
    }
    fmt.Printf("Valid age: %d
", age)
    
    // Invalid age
    _, err = validateAge("abc")
    if err != nil {
        fmt.Printf("Validation error: %v
", err)
    }
    
    // Negative age
    _, err = validateAge("-5")
    if err != nil {
        fmt.Printf("Validation error: %v
", err)
    }
    
    // File processing with defer
    err = processFile("data.txt")
    if err != nil {
        fmt.Printf("File error: %v
", err)
    }
    
    // Error wrapping
    originalErr := errors.New("original error")
    wrappedErr := fmt.Errorf("wrapped error: %w", originalErr)
    fmt.Printf("Wrapped error: %v
", wrappedErr)
    
    // Error unwrapping
    if unwrappedErr := errors.Unwrap(wrappedErr); unwrappedErr != nil {
        fmt.Printf("Unwrapped error: %v
", unwrappedErr)
    }
}

Code Editor

Output