> For the complete documentation index, see [llms.txt](https://ayushs-organization-15.gitbook.io/learn-go-today/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ayushs-organization-15.gitbook.io/learn-go-today/foundations/errors-in-go-values-not-exceptions.md).

# Errors in Go: Values, Not Exceptions

### 1. Go’s Error Philosophy

* **Errors are values.** They are not disruptive control flow tools, but part of the normal data returned from functions.
* **No exceptions.** There is no `try/catch` or stack unwinding. You handle errors where they occur.
* **Explicit is better than implicit.** Go prefers repetitive clarity (`if err != nil`) over hidden or magical error handling.

> “Errors are just values. They’re part of the normal flow of control.” — Rob Pike, Go co-creator

***

### 2. The Basics: Returning Errors Explicitly

A function that can fail typically returns two values: the result (if any) and an error.

```go
import (
    "errors"
    "fmt"
)

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}
```

* If `b` is zero, we return a custom error. Otherwise, the division proceeds and `err` is `nil`.

***

### 3. Idiomatic Checking: `if err != nil`

This is the most common pattern in Go:

```go
value, err := someFunc()
if err != nil {
    // handle error
}
```

* **Why so repetitive?**
  * You always see where errors might occur—no hidden control flow.
  * You decide how to handle each error, right after the function call.

***

### 4. Creating and Propagating Errors

#### Creating Errors

* Use `errors.New("message")` for simple errors.
* Use `fmt.Errorf("context: %w", err)` to wrap errors with context.

```go
import (
    "errors"
    "fmt"
)

func readConfig() error {
    return errors.New("config file not found")
}

func loadApp() error {
    err := readConfig()
    if err != nil {
        return fmt.Errorf("loadApp failed: %w", err)
    }
    return nil
}
```

* `%w` in `fmt.Errorf` wraps the original error, preserving the trace.

***

### 5. Sentinel Errors: Known, Comparable Values

You can define specific errors for comparison:

```go
var ErrNotFound = errors.New("not found")

func findUser(id int) error {
    // ...
    return ErrNotFound
}

if errors.Is(err, ErrNotFound) {
    // handle not found
}
```

* Use `errors.Is()` for type-safe error comparison.

***

### 6. Custom Error Types

For richer error data, define your own error type:

```go
type HTTPError struct {
    Code int
    Message string
}

func (e *HTTPError) Error() string {
    return fmt.Sprintf("HTTP %d: %s", e.Code, e.Message)
}

func fetch() error {
    return &HTTPError{Code: 404, Message: "Not Found"}
}

err := fetch()
if httpErr, ok := err.(*HTTPError); ok {
    fmt.Println("Status code:", httpErr.Code)
}
```

* Use `errors.As()` to check and extract custom error types.

***

### 7. Best Practices

* Handle errors immediately or explicitly propagate them.
* Never ignore errors (don’t use `_` unless you’re absolutely sure).
* Use `fmt.Errorf` for adding context.
* Prefer `errors.Is()` and `errors.As()` for type-safe matching.
* Keep error messages clear and user-focused.
* Avoid panics for normal error handling (use only for truly unrecoverable situations).

***

### 8. Anti-Patterns

* **Don’t suppress errors:** Always check and handle them.
* **Don’t use panic/recover for flow control:** Reserve for bugs or truly exceptional cases.
* **Don’t rely on string-matching error messages:** Use sentinel errors or custom types.

***

### 9. Real-World Example: Error Propagation in Cloud APIs

Suppose you’re building a cloud service that fetches metadata from a provider and saves it to a database. Each step can fail:

```go
func fetchMetadata() (Metadata, error) { /* ... */ }
func saveToDB(data Metadata) error { /* ... */ }

func process() error {
    data, err := fetchMetadata()
    if err != nil {
        return fmt.Errorf("fetch failed: %w", err)
    }
    if err := saveToDB(data); err != nil {
        return fmt.Errorf("db save failed: %w", err)
    }
    return nil
}

func main() {
    if err := process(); err != nil {
        log.Fatalf("process failed: %v", err)
    }
}
```

* Error propagation is explicit and context-aware.
* Zero values ensure structs like `Metadata` are safe to use even before initialization.
* Short `if err != nil` patterns keep logic close to where failures can occur.
* `defer` can be used for resource cleanup (e.g., closing files or network connections).

***

### 10. Go vs. Exception-Based Languages

* **No hidden control flow:** Errors are handled where they occur, not by unwinding the stack.
* **No try/catch:** You always see error handling logic.
* **No stack trace drama:** You’re in control of every potential failure and its propagation.
* **Composability:** Errors can be stored, wrapped, compared, or logged like any other value.

***

### 11. Practice & Conceptual Questions

1. Write a function that returns an error if a file does not exist.
2. Create a custom error type for HTTP errors and demonstrate type assertion.
3. Use `fmt.Errorf` to wrap an error with context and extract the original error using `errors.Is`.
4. Refactor a function to propagate errors up the call stack with context.
5. Show how to use `defer` for resource cleanup in the presence of errors.
6. Compare Go’s error handling to exception-based languages. What are the trade-offs?

***

### Further Reading

* [Go Blog: Error Handling and Go](https://blog.golang.org/error-handling-and-go)
* [Go by Example: Errors](https://gobyexample.com/errors)
* [Effective Go: Errors](https://golang.org/doc/effective_go#errors)
