Error Handling¶
Go doesn't support throw, try, catch and other common error handling structures. Instead, errors are values that are returned from functions.
The Error Interface¶
Basic Error Handling¶
import "errors"
// 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
}
// Using the function
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result) // Result: 5
Creating Errors¶
Using errors.New¶
import "errors"
func validateAge(age int) error {
if age < 0 {
return errors.New("age cannot be negative")
}
if age > 150 {
return errors.New("age is too high")
}
return nil
}
Using fmt.Errorf¶
import "fmt"
func findUser(id int) (*User, error) {
if id < 0 {
return nil, fmt.Errorf("invalid user ID: %d", id)
}
// ...
return nil, fmt.Errorf("user %d not found", id)
}
Custom Error Types¶
// Define custom error type
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("validation error on field '%s': %s", e.Field, e.Message)
}
// Using custom error
func validateEmail(email string) error {
if !strings.Contains(email, "@") {
return ValidationError{
Field: "email",
Message: "must contain @ symbol",
}
}
return nil
}
// Check for specific error type
err := validateEmail("invalid")
if err != nil {
if ve, ok := err.(ValidationError); ok {
fmt.Printf("Field: %s, Message: %s\n", ve.Field, ve.Message)
}
}
Error Wrapping (Go 1.13+)¶
import (
"errors"
"fmt"
)
// Wrap errors with context
func readConfig(filename string) error {
err := openFile(filename)
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
}
return nil
}
// Unwrap errors
err := readConfig("config.json")
if err != nil {
// Get the underlying error
unwrapped := errors.Unwrap(err)
fmt.Println(unwrapped)
}
Checking for Specific Errors¶
Using errors.Is¶
import "errors"
var ErrNotFound = errors.New("not found")
func findItem(id int) error {
return ErrNotFound
}
err := findItem(1)
if errors.Is(err, ErrNotFound) {
fmt.Println("Item not found")
}
Using errors.As¶
type NetworkError struct {
Code int
Msg string
}
func (e NetworkError) Error() string {
return fmt.Sprintf("network error %d: %s", e.Code, e.Msg)
}
func makeRequest() error {
return NetworkError{Code: 500, Msg: "server error"}
}
err := makeRequest()
var netErr NetworkError
if errors.As(err, &netErr) {
fmt.Printf("Network error code: %d\n", netErr.Code)
}
Sentinel Errors¶
import "errors"
// Define sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func getUser(id int) (*User, error) {
if id < 0 {
return nil, ErrInvalidInput
}
// ...
return nil, ErrNotFound
}
// Usage
user, err := getUser(-1)
if err != nil {
switch err {
case ErrNotFound:
fmt.Println("User not found")
case ErrUnauthorized:
fmt.Println("Access denied")
case ErrInvalidInput:
fmt.Println("Invalid input")
}
}
Multiple Return Values¶
// Common pattern: return result and error
func processData(data string) (string, error) {
if data == "" {
return "", errors.New("data is empty")
}
result := strings.ToUpper(data)
return result, nil
}
// Usage
result, err := processData("hello")
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
Panic and Recover¶
Use panic for unrecoverable errors. Use recover to catch panics.
// Panic
func mustConnect(url string) {
if url == "" {
panic("URL cannot be empty")
}
// connect...
}
// Recover
func safeExecute() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
// Code that might panic
mustConnect("")
}
safeExecute() // Recovered from panic: URL cannot be empty
Best Practices¶
1. Always Check Errors¶
2. Add Context to Errors¶
3. Don't Ignore Errors¶
// Bad
file.Close()
// Good
if err := file.Close(); err != nil {
log.Printf("failed to close file: %v", err)
}
4. Return Early¶
// Good
func process(data string) error {
if data == "" {
return errors.New("data is empty")
}
result, err := transform(data)
if err != nil {
return fmt.Errorf("transform failed: %w", err)
}
if err := save(result); err != nil {
return fmt.Errorf("save failed: %w", err)
}
return nil
}
Error Handling Patterns¶
Retry Pattern¶
func retryOperation(maxRetries int, operation func() error) error {
var err error
for i := 0; i < maxRetries; i++ {
err = operation()
if err == nil {
return nil
}
time.Sleep(time.Second * time.Duration(i+1))
}
return fmt.Errorf("operation failed after %d retries: %w", maxRetries, err)
}
Error Aggregation¶
type MultiError struct {
Errors []error
}
func (m MultiError) Error() string {
var msgs []string
for _, err := range m.Errors {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
func validateUser(user User) error {
var errs []error
if user.Name == "" {
errs = append(errs, errors.New("name is required"))
}
if user.Email == "" {
errs = append(errs, errors.New("email is required"))
}
if user.Age < 0 {
errs = append(errs, errors.New("age must be positive"))
}
if len(errs) > 0 {
return MultiError{Errors: errs}
}
return nil
}