Mastering Go’s errors package: New, Unwrap, Is, and As explained

This article explains Go's built‑in error interface and the standard library errors package, covering how to create errors with New, unwrap nested errors, compare errors with Is, and extract concrete error types with As, including custom error examples.

TiPaiPai Technical Team
TiPaiPai Technical Team
TiPaiPai Technical Team
Mastering Go’s errors package: New, Unwrap, Is, and As explained

What is an error in Go?

The error type is a built‑in interface with a single method Error() string; any type implementing this method satisfies the error interface, but it only carries a string message.

The errors package

The package provides four core functions:

func New(text string) error
func Unwrap(err error) error
func Is(err, target error) bool
func As(err error, target interface{}) bool

New

errors.New

returns a distinct error value containing the supplied text. Internally it creates an errorString struct with a single s string field.

type errorString struct { s string }
func (e *errorString) Error() string { return e.s }
func New(text string) error { return &errorString{text} }

You can define custom error types, for example a curlError that stores the request URL together with the message:

type curlError struct { s string; url string }
func (e *curlError) Error() string { return e.url + ":" + e.s }
func NewCurlError(text, url string) error { return &curlError{text, url} }

Unwrap

errors.Unwrap

returns the next error in a chain if the error implements an Unwrap() error method; otherwise it returns nil. Repeated calls peel off each layer.

func Unwrap(err error) error {
    if u, ok := err.(interface{ Unwrap() error }); ok {
        return u.Unwrap()
    }
    return nil
}

Is

errors.Is

checks whether an error matches a target error, either by direct equality, by calling the target's Is(error) bool method, or by traversing the unwrap chain.

func Is(err, target error) bool {
    if target == nil {
        return err == nil
    }
    if isComparable && err == target {
        return true
    }
    if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
        return true
    }
    if err = Unwrap(err); err == nil {
        return false
    }
    // repeat loop
}

As

errors.As

finds the first error in the chain that can be assigned to a variable of a specific concrete type. It requires the target to be a non‑nil pointer to an interface or error type.

func As(err error, target interface{}) bool {
    if target == nil { panic("target cannot be nil") }
    val := reflect.ValueOf(target)
    typ := val.Type()
    if typ.Kind() != reflect.Ptr || val.IsNil() { panic("target must be a non‑nil pointer") }
    targetType := typ.Elem()
    for err != nil {
        if reflect.TypeOf(err).AssignableTo(targetType) {
            val.Elem().Set(reflect.ValueOf(err))
            return true
        }
        if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
            return true
        }
        err = Unwrap(err)
    }
    return false
}

When you need to work with a custom error's fields, use errors.As to retrieve the concrete value and then call its methods or access its fields.

GolangError HandlingStandard Libraryasisunwraperrors
TiPaiPai Technical Team
Written by

TiPaiPai Technical Team

At TiPaiPai, we focus on building engineering teams and culture, cultivating technical insights and practice, and fostering sharing, growth, and connection.

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.