Fundamentals 13 min read

Mastering Go 1.13 Error Handling: Unwrap, Is, As, and %w

Go 1.13 introduced new error‑handling utilities—Unwrap methods, errors.Is and errors.As functions, and the %w formatting verb—allowing developers to wrap, inspect, and propagate underlying errors more effectively, with examples illustrating legacy patterns, custom error types, and best‑practice decisions on when to wrap.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
Mastering Go 1.13 Error Handling: Unwrap, Is, As, and %w

Introduction

Early Go treated errors as ordinary values, and the standard library only provided errors.New and fmt.Errorf, which produced errors containing a single message. The built‑in error interface lets developers embed any additional information by defining a type that implements the Error() method.

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }

Such custom error types are ubiquitous; they may carry timestamps, file names, server addresses, or a lower‑level error for extra context.

Pre‑Go 1.13 Error Handling

Checking Errors

Typical error checks compare the error value to nil or to a sentinel value.

if err != nil {
    // handle error
}

var ErrNotFound = errors.New("not found")
if err == ErrNotFound {
    // not found handling
}

Errors can also be type‑asserted or type‑switched to a more specific struct.

type NotFoundError struct { Name string }
func (e *NotFoundError) Error() string { return e.Name + ": not found" }
if e, ok := err.(*NotFoundError); ok {
    // e.Name is the missing item
}

Adding Context

When propagating an error up the call stack, developers often add a short description.

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

Creating a new error type that embeds the underlying error (e.g., QueryError) enables “unpacking” the error later.

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed due to permission issue
}

Go 1.13 and Later Error Handling

Unwrap Method

Go 1.13 adds a convention: an error that wraps another implements an Unwrap() error method returning the inner error. This creates an error chain.

func (e *QueryError) Unwrap() error { return e.Err }

Using errors.Is and errors.As

The new errors.Is function checks whether any error in the chain matches a target value (similar to sentinel comparison). errors.As checks whether any error in the chain can be assigned to a target type.

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

var e *QueryError
if errors.As(err, &e) {
    // err is a *QueryError, e holds the concrete value
}

Both functions traverse the entire chain, so they work with wrapped errors as well.

Formatting with %w

fmt.Errorf

now supports the %w verb. When used, the returned error implements Unwrap and returns the wrapped error.

if err != nil {
    return fmt.Errorf("decompress %v: %w", name, err)
}

err := fmt.Errorf("access denied: %w", ErrPermission)
if errors.Is(err, ErrPermission) {
    // permission error detected
}

When to Wrap Errors

Whether to wrap depends on the API contract. If exposing the underlying error is part of the public contract, wrap with %w. If the underlying error is an implementation detail, either omit it or wrap with %v to hide the inner type.

// Example: exposing sql.ErrNoRows deliberately
err := pkg.LookupUser(...)
if errors.Is(err, sql.ErrNoRows) { /* handle missing row */ }

// Example: hiding internal *os.PathError
if err != nil {
    return fmt.Errorf("%v", err) // %v hides the concrete type
}

Custom Is Method

Types can implement an Is(error) bool method to define custom matching logic, as shown by the Upspin‑inspired example.

type Error struct { Path, User string }
func (e *Error) Is(target error) bool {
    t, ok := target.(*Error)
    if !ok { return false }
    return (e.Path == t.Path || t.Path == "") &&
           (e.User == t.User || t.User == "")
}
if errors.Is(err, &Error{User: "someuser"}) {
    // err's User field matches "someuser"
}

Errors and Package APIs

A well‑designed package should document which errors callers can rely on and avoid leaking internal details. The simplest contract is “nil on success, non‑nil on failure.” When a specific condition must be signaled, return a wrapped sentinel error.

var ErrNotFound = errors.New("not found")
func FetchItem(name string) (*Item, error) {
    if itemNotFound(name) {
        return nil, fmt.Errorf("%q: %w", name, ErrNotFound)
    }
    // ...
}

When returning errors from another package, decide whether the underlying error should be part of the public API. If not, wrap it with %v or a custom error type that hides the implementation detail.

if err != nil {
    return fmt.Errorf("%v", err) // hides *os.PathError
}

Conclusion

Go 1.13’s three new functions ( errors.Is, errors.As, errors.Unwrap) and the %w formatting verb dramatically improve error handling by making error chains explicit, encouraging consistent wrapping for context, and giving callers reliable tools to inspect and react to errors.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

errors-packagego1.13unwraperror-handlingerrors.aserrors.isfmt.Errorf
Ops Development & AI Practice
Written by

Ops Development & AI Practice

DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.