Error handling is a core part of Go programming. Go uses explicit error returns rather than exceptions, making error handling clear and intentional. For more context, see Go Installation Guide, Go Ecosystem Overview, Go Best Practices.
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