Error Handling
Error Handling in Go
Go handles errors explicitly by returning them as values — there are no exceptions.
The error interface
error is a built-in interface with one method:
go
type error interface { Error() string }Return nil for success, a non-nil error for failure.
Creating errors
errors.New("message")— simple error value.fmt.Errorf("context: %w", err)— formats a message and optionally wraps an existing error.
Wrapping errors with %w
Wrapping preserves the original error for inspection:
go
err = fmt.Errorf("open config: %w", originalErr)errors.Is and errors.As
errors.Is(err, target)— checks if any error in the chain equals target (useful for sentinel errors).errors.As(err, &target)— unwraps and assigns the first error of target's type.
Key points:
- Always check errors immediately after the call that produced them.
- The pattern
if err != nil { return …, err }propagates errors up the call stack. - Wrapping with
%wlets callers inspect the chain without exposing internal details.
Code Examples
Returning and checking errorsgo
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findUser(id int) (string, error) {
users := map[int]string{1: "Alice", 2: "Bob"}
name, ok := users[id]
if !ok {
return "", fmt.Errorf("findUser(%d): %w", id, ErrNotFound)
}
return name, nil
}
func main() {
name, err := findUser(1)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Found:", name)
}
_, err = findUser(99)
if err != nil {
fmt.Println("Error:", err)
fmt.Println("Is ErrNotFound:", errors.Is(err, ErrNotFound))
}
}Sentinel errors like ErrNotFound allow callers to check for specific conditions with errors.Is, even when the error is wrapped.
Custom error types and errors.Asgo
package main
import (
"errors"
"fmt"
)
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: "must be non-negative"}
}
if age > 150 {
return &ValidationError{Field: "age", Message: "unrealistically large"}
}
return nil
}
func main() {
err := validateAge(-5)
if err != nil {
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Printf("Field: %s\nMessage: %s\n", ve.Field, ve.Message)
}
}
}errors.As unwraps an error chain looking for a specific type. Custom error types carry structured information beyond a plain string.
Quick Quiz
1. What does `fmt.Errorf("context: %w", err)` do to the original error?
2. What is the zero value of an `error` indicating success?
Was this lesson helpful?