Skip to main content

Github

🩺 Error Handling in Go

Error Handling in Go #

Go’s approach to error handling is explicit and straightforward. Errors are values that are returned from functions, not thrown as exceptions.

The error Type #

The error type is a built-in interface:

type error interface {
 Error() string
}
// Using errors
import "errors"
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, 0)
 if err != nil {
 fmt.Println("Error:", err)
 return
 }
 fmt.Println("Result:", result)
}

Creating Custom Errors #

Create custom error types by implementing the error interface:

type ValidationError struct {
 Field string
 Message string
}
func (e *ValidationError) Error() string {
 return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}
func validateAge(age int) error {
 if age < 0 {
 return &ValidationError{
 Field: "age",
 Message: "cannot be negative",
 }
 }
 return nil
}

Error Wrapping #

Wrap errors to add context using fmt.Errorf with %w:

import (
 "fmt"
 "errors"
)
func readConfig(filename string) error {
 _, err := os.Open(filename)
 if err != nil {
 return fmt.Errorf("failed to read config: %w", err)
 }
 return nil
}
func main() {
 err := readConfig("config.json")
 if err != nil {
 fmt.Println(err)
 
 // Unwrap to check original error
 if errors.Is(err, os.ErrNotExist) {
 fmt.Println("Config file not found")
 }
 }
}

Error Checking Patterns #

Common patterns for checking specific errors:

// errors.Is - check if error matches a value
if errors.Is(err, os.ErrNotExist) {
 // Handle file not found
}
// errors.As - check if error is of a specific type
var validationErr *ValidationError
if errors.As(err, &validationErr) {
 fmt.Println("Field:", validationErr.Field)
}
// Type assertion
if netErr, ok := err.(*net.Error); ok {
 if netErr.Timeout() {
 // Handle timeout
 }
}

Panic and Recover #

For unrecoverable errors, use panic and recover:

func riskyOperation() {
 defer func() {
 if r := recover(); r != nil {
 fmt.Println("Recovered from:", r)
 }
 }()
 
 // This will panic
 panic("something went wrong")
}
// Use panic sparingly, only for truly exceptional situations
func mustConnect(addr string) *Connection {
 conn, err := connect(addr)
 if err != nil {
 panic(fmt.Sprintf("failed to connect to %s: %v", addr, err))
 }
 return conn
}

Best Practices #

Guidelines for effective error handling:

// 1. Always check errors
f, err := os.Open("file.txt")
if err != nil {
 return err
}
defer f.Close()
// 2. Provide context with error messages
if err != nil {
 return fmt.Errorf("failed to process user %d: %w", userID, err)
}
// 3. Handle errors at the appropriate level
func processFile(filename string) error {
 // Handle specific errors here
 if err := validateFile(filename); err != nil {
 return err
 }
 
 // Let unexpected errors bubble up
 return processData(filename)
}
// 4. Use named return values for error handling
func operation() (result int, err error) {
 defer func() {
 if err != nil {
 err = fmt.Errorf("operation failed: %w", err)
 }
 }()
 
 // Do work...
 return
}

Related

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /