go-errors

Errors are values in Go. Handle them explicitly — don't hide them, don't panic for expected failures.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "go-errors" with this command: npx skills add peixotorms/odinlayer-skills/peixotorms-odinlayer-skills-go-errors

Go Error Handling

Core Principle

Errors are values in Go. Handle them explicitly — don't hide them, don't panic for expected failures.

Error Interface

type error interface { Error() string }

Creating Errors

// Simple errors import "errors" var ErrNotFound = errors.New("not found")

// Formatted errors import "fmt" return fmt.Errorf("user %d not found", id)

// Wrapped errors (Go 1.13+) — preserves cause chain return fmt.Errorf("fetching user: %w", err)

// Custom error type type ValidationError struct { Field string Message string }

func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) }

Error Wrapping & Unwrapping

// Wrap with context if err := db.Query(sql); err != nil { return fmt.Errorf("querying users: %w", err) }

// errors.Is — check for specific error in chain if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound }

// errors.As — extract specific error type from chain var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Println("Failed at path:", pathErr.Path) }

Verb Effect

%w

Wraps error — errors.Is / errors.As can unwrap

%v

Formats error — wrapping chain is lost

When to Wrap vs Not

Scenario Action Why

Adding context fmt.Errorf("...: %w", err)

Preserves chain for callers

Hiding implementation detail fmt.Errorf("...: %v", err) or new error Don't leak internal types

Matching sentinel errors Use %w

Callers can errors.Is

Library boundary Consider carefully Wrapping couples caller to your dependencies

Sentinel Errors

// Package-level error values var ( ErrNotFound = errors.New("not found") ErrUnauthorized = errors.New("unauthorized") ErrConflict = errors.New("conflict") )

// Usage func FindUser(id int) (*User, error) { user, err := db.Get(id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } return nil, fmt.Errorf("finding user %d: %w", id, err) } return user, nil }

Sentinel rule Detail

Name with Err prefix ErrNotFound , ErrTimeout

Package-level var

Not const — must be comparable with errors.Is

Use sparingly Only for errors callers need to check programmatically

Document them Part of your API contract

Custom Error Types

type NotFoundError struct { Resource string ID int }

func (e *NotFoundError) Error() string { return fmt.Sprintf("%s %d not found", e.Resource, e.ID) }

// Optional: wrap underlying error type QueryError struct { Query string Err error }

func (e *QueryError) Error() string { return fmt.Sprintf("query %q: %v", e.Query, e.Err) }

func (e *QueryError) Unwrap() error { return e.Err // enables errors.Is/errors.As }

Errors as Values

Errors are programmable values — use patterns to reduce repetitive if err != nil :

// ErrorWriter: accumulates error, check once at end type ErrorWriter struct { w io.Writer err error }

func (ew *ErrorWriter) Write(buf []byte) { if ew.err != nil { return } _, ew.err = ew.w.Write(buf) }

// Usage — chain writes, check once ew := &ErrorWriter{w: w} ew.Write(header) ew.Write(body) ew.Write(footer) if ew.err != nil { return ew.err }

No In-Band Errors

// BAD: -1 or empty string as error signal func Lookup(key string) string { if _, ok := m[key]; !ok { return "" } // "" might be valid! return m[key] }

// GOOD: return additional value func Lookup(key string) (string, bool) { v, ok := m[key] return v, ok }

// GOOD: return error func Lookup(key string) (string, error) { v, ok := m[key] if !ok { return "", ErrNotFound } return v, nil }

Handle Every Error

// BAD: silently ignored f.Close()

// GOOD: handle or explicitly acknowledge if err := f.Close(); err != nil { log.Printf("closing file: %v", err) }

// OK: documented why it's safe to ignore n, _ := buf.Write(data) // bytes.Buffer.Write never returns error

Handling Patterns

The Standard Pattern

result, err := doSomething() if err != nil { return fmt.Errorf("doing something: %w", err) } // use result

Multiple Cleanup Actions

func process() (err error) { f, err := os.Open("file.txt") if err != nil { return err } defer f.Close()

w, err := os.Create("output.txt")
if err != nil {
    return err
}
defer func() {
    closeErr := w.Close()
    if err == nil {
        err = closeErr  // capture close error if no prior error
    }
}()

_, err = io.Copy(w, f)
return err

}

Error Type Switch

switch err := err.(type) { case nil: // success case *ValidationError: http.Error(w, err.Message, http.StatusBadRequest) case *NotFoundError: http.Error(w, err.Error(), http.StatusNotFound) default: log.Printf("unexpected error: %v", err) http.Error(w, "internal error", http.StatusInternalServerError) }

Panic & Recover

Panic

// Panic for truly unrecoverable situations func MustCompile(pattern string) *Regexp { re, err := Compile(pattern) if err != nil { panic("regexp: Compile(" + pattern + "): " + err.Error()) } return re }

Panic rule Detail

Only for programmer errors Impossible states, violated invariants

Never for expected failures Network errors, file not found → return error

Must prefix convention MustCompile , MustParse — panics on error

Acceptable in init()

If package cannot initialize

Recover

func safeHandler(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("panic recovered: %v\n%s", err, debug.Stack()) http.Error(w, "internal error", http.StatusInternalServerError) } }() handleRequest(w, r) }

Recover rule Detail

Only in deferred functions recover() returns nil outside defer

Stops unwinding Returns panic value

Use at goroutine/request boundary Prevent one failure from crashing server

Always log the panic With stack trace (debug.Stack() )

Don't recover to hide bugs Fix the root cause

Common Mistakes

Mistake Why Bad Fix

_ = f.Close()

Silently loses write errors if err := f.Close(); err != nil { ... }

panic for expected errors Crashes program Return error

Not wrapping errors Lost context fmt.Errorf("context: %w", err)

Comparing errors with ==

Doesn't check wrapped chain errors.Is(err, target)

Type-asserting errors directly Doesn't check wrapped chain errors.As(err, &target)

Wrapping with %v when %w intended Breaks errors.Is / errors.As

Use %w for wrapping

Error strings starting with capital Convention violation Lowercase: "opening file: ..."

Error strings ending with punctuation Doesn't compose well No period: "not found" not "not found."

if err != nil { return err } everywhere Lost context Add wrapping message

Returning error AND valid value Confusing API If err != nil , zero-value the result

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

elementor-hooks

No summary provided by upstream source.

Repository SourceNeeds Review
General

elementor-themes

No summary provided by upstream source.

Repository SourceNeeds Review
General

elementor-controls

No summary provided by upstream source.

Repository SourceNeeds Review