PY
GO

Python to Go

10 lessons

Progress0%
1Variables & Types2Functions3Lists → Slices, Dicts → Maps4Classes → Structs5asyncio → Goroutines6Modules → Packages7Interfaces8Error Handling Patterns9Testing10Standard Library
All Mirror Courses
PY
GO
Error Handling Patterns
MirrorLesson 8 of 10
Lesson 8

Error Handling Patterns

Go error values vs Python exceptions — wrapping, unwrapping, and sentinel errors

Introduction

In this lesson, you'll learn about error handling patterns in Go. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
PY
From Python:

In Python, you're familiar with go error values vs python exceptions — wrapping, unwrapping, and sentinel errors.

GO
In Go:

Go has its own approach to go error values vs python exceptions — wrapping, unwrapping, and sentinel errors, which we'll explore step by step.

The Go Way

Let's see how Go handles this concept. Here's a typical example:

GO
Go Example
package main

import (
    "errors"
    "fmt"
)

// Custom error type
type NotFoundError struct {
    Resource string
    ID       int
}
func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s %d not found", e.Resource, e.ID)
}

// Sentinel errors (like Python's exception classes used as signals)
var ErrNotFound = errors.New("not found")
var ErrPermission = errors.New("permission denied")

// Function that returns error
func GetUser(id int) (map[string]any, error) {
    if id < 0 {
        return nil, fmt.Errorf("id must be positive, got %d", id)
    }
    if id > 100 {
        return nil, &NotFoundError{Resource: "User", ID: id}
    }
    return map[string]any{"id": id}, nil
}

// Error wrapping with %w
func LoadConfig(path string) (Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return Config{}, fmt.Errorf("loadConfig %s: %w", path, err)
    }
    return parseConfig(data)
}

func main() {
    user, err := GetUser(999)
    if err != nil {
        var nfe *NotFoundError
        if errors.As(err, &nfe) {
            fmt.Printf("Not found: %s #%d\n", nfe.Resource, nfe.ID)
        } else {
            fmt.Println("Error:", err)
        }
        return
    }
    _ = user
}

Comparing to Python

Here's how you might have written similar code in Python:

PY
Python (What you know)
# Custom exception hierarchy
class AppError(Exception):
    pass

class NotFoundError(AppError):
    def __init__(self, resource: str, id: int):
        super().__init__(f"{resource} {id} not found")
        self.resource = resource
        self.id = id

# Raise and catch
def get_user(id: int):
    if id < 0:
        raise ValueError("id must be positive")
    if id > 100:
        raise NotFoundError("User", id)
    return {"id": id}

try:
    user = get_user(999)
except NotFoundError as e:
    print(f"Not found: {e.resource} #{e.id}")
except ValueError as e:
    print(f"Bad input: {e}")

# Chaining exceptions
try:
    parse_config()
except json.JSONDecodeError as e:
    raise AppError("config invalid") from e
Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

GO
In Go:

Go returns errors as values; Python throws exceptions — completely different control flow

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

GO
In Go:

errors.As extracts concrete error type — equivalent to except NotFoundError as e

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

GO
In Go:

errors.Is checks sentinel errors through the chain — like checking exception type

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

GO
In Go:

fmt.Errorf with %w wraps errors (like Python 'raise X from Y')

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

GO
In Go:

Go has no try/catch — caller must check err != nil after every call

Step-by-Step Breakdown

1. Errors as Values

Go errors are values returned alongside results. Every fallible function returns (result, error). Check error before using result.

PY
Python
try:
    user = get_user(id)
except NotFoundError:
    handle()
GO
Go
user, err := GetUser(id)
if err != nil {
    handle(err)
    return
}
// safe to use user here
Rule of Thumb
Handle errors immediately after the call that produced them. Don't ignore errors.

2. Custom Error Types

Implement the error interface (Error() string) for custom error types. Use pointer receivers so errors.As can match.

PY
Python
class NotFoundError(Exception):
    def __init__(self, id): self.id = id
GO
Go
type NotFoundError struct { ID int }
func (e *NotFoundError) Error() string {
    return fmt.Sprintf("not found: %d", e.ID)
}

3. Error Wrapping

Wrap errors with context using fmt.Errorf("%w", err). This preserves the original error so errors.Is/As can unwrap the chain.

PY
Python
try:
    parse()
except json.JSONDecodeError as e:
    raise AppError("parse failed") from e
GO
Go
if err := parse(); err != nil {
    return fmt.Errorf("parse failed: %w", err)
    // %w wraps; caller can errors.Is(err, originalErr)
}

4. errors.As and errors.Is

errors.As extracts a concrete type from an error chain. errors.Is checks for sentinel errors. Both unwrap through the chain.

PY
Python
except NotFoundError as e: ...
except PermissionError: ...
GO
Go
var nfe *NotFoundError
if errors.As(err, &nfe) { /* nfe is populated */ }

if errors.Is(err, ErrNotFound) { /* matches sentinel */ }
Common Pitfall
errors.As requires a pointer to the target type: errors.As(err, &target) not errors.As(err, target).

Common Mistakes

When coming from Python, developers often make these mistakes:

  • Go returns errors as values; Python throws exceptions — completely different control flow
  • errors.As extracts concrete error type — equivalent to except NotFoundError as e
  • errors.Is checks sentinel errors through the chain — like checking exception type
Common Pitfall
Don't assume Go works exactly like Python. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Go errors are return values — check every err != nil; no try/catch
  • Custom errors: implement Error() string interface; use pointer receiver
  • fmt.Errorf("%w", err) wraps errors with context — like 'raise X from Y'
  • errors.As extracts concrete type from chain; errors.Is checks sentinel values
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your Python code in Go to practice these concepts.
PreviousNext