Error Handling in Go
Error handling is a core part of Go programming. Go uses explicit error returns rather than exceptions, making error handling clear and intentional.
Error Interface
Understanding the Error Interface
package main
import "fmt"
// Error interface definition
type error interface {
Error() string
}
// Using built-in errors
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, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
Error Checking Patterns
Basic Error Checking
package main
import (
"fmt"
"os"
)
func main() {
// Check error immediately
file, err := os.Open("file.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()
// Use file...
}
Multiple Error Checks
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
// Read file
data, err := ioutil.ReadFile("file.txt")
if err != nil {
fmt.Println("Read error:", err)
return
}
// Write file
err = ioutil.WriteFile("output.txt", data, 0644)
if err != nil {
fmt.Println("Write error:", err)
return
}
fmt.Println("Success")
}
Custom Errors
Creating Custom Error Types
package main
import (
"fmt"
)
// Custom error type
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 validateEmail(email string) error {
if email == "" {
return &ValidationError{
Field: "email",
Message: "email cannot be empty",
}
}
return nil
}
func main() {
err := validateEmail("")
if err != nil {
fmt.Println(err)
}
}
Error with Context
package main
import (
"fmt"
)
type AppError struct {
Code int
Message string
Err error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
func main() {
err := &AppError{
Code: 500,
Message: "Internal Server Error",
Err: fmt.Errorf("database connection failed"),
}
fmt.Println(err)
}
Error Wrapping
Using fmt.Errorf
package main
import (
"fmt"
"os"
)
func readConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config: %w", err)
}
return data, nil
}
func main() {
_, err := readConfig("config.json")
if err != nil {
fmt.Println(err)
}
}
Unwrapping Errors
package main
import (
"errors"
"fmt"
"os"
)
func main() {
_, err := os.Open("nonexistent.txt")
if err != nil {
// Check if it's a specific error type
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File not found")
}
// Get underlying error
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Path: %s\n", pathErr.Path)
}
}
}
Error Handling Patterns
Early Return Pattern
package main
import (
"fmt"
)
func processData(data string) error {
if data == "" {
return fmt.Errorf("data cannot be empty")
}
if len(data) > 100 {
return fmt.Errorf("data too long")
}
// Process data...
return nil
}
func main() {
err := processData("test")
if err != nil {
fmt.Println("Error:", err)
}
}
Defer for Cleanup
package main
import (
"fmt"
"os"
)
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
// Process file...
return nil
}
func main() {
err := processFile("data.txt")
if err != nil {
fmt.Println(err)
}
}
Panic and Recover
package main
import (
"fmt"
)
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
result = 0
}
}()
if b == 0 {
panic("division by zero")
}
return a / b
}
func main() {
fmt.Println(safeDivide(10, 2)) // 5
fmt.Println(safeDivide(10, 0)) // 0 (recovered)
}
Best Practices
โ Good: Explicit Error Handling
// DO: Check errors explicitly
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
โ Bad: Ignoring Errors
// DON'T: Ignore errors
_ = someFunction()
โ Good: Wrap Errors with Context
// DO: Add context when wrapping errors
if err != nil {
return fmt.Errorf("failed to read config from %s: %w", path, err)
}
โ Good: Use Sentinel Errors
// DO: Define sentinel errors for comparison
var ErrNotFound = errors.New("not found")
var ErrInvalid = errors.New("invalid input")
โ Bad: Generic Error Messages
// DON'T: Use vague error messages
return fmt.Errorf("error")
Summary
Go’s error handling provides:
- Explicit error returns for clarity
- Error interface for custom errors
- Error wrapping for context
- Error checking patterns for safety
- Panic/recover for exceptional cases
These features make error handling explicit and intentional in Go programs.
Comments