Skip to content
Go Basics: Preserve Error Identity Through Context

Go Basics: Preserve Error Identity Through Context

October 31, 2026

Callers need useful context and a machine-readable reason for failure. Wrap the underlying error instead of comparing human-readable strings.

Prepare a scratch directory

Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as wrapped-errors-demo.go:

package main

import (
	"errors"
	"fmt"
	"strconv"
)

var ErrPortRange = errors.New("port out of range")

func port(text string) (int, error) {
	n, err := strconv.Atoi(text)
	if err != nil {
		return 0, fmt.Errorf("parse port: %w", err)
	}
	if n < 1 || n > 65535 {
		return 0, fmt.Errorf("port %d: %w", n, ErrPortRange)
	}
	return n, nil
}
func main() {
	for _, input := range []string{"8080", "70000", "oops"} {
		n, err := port(input)
		var parseErr *strconv.NumError
		fmt.Printf("%q: port=%d range=%t parse=%t\n", input, n,
			errors.Is(err, ErrPortRange), errors.As(err, &parseErr))
		if parseErr != nil {
			fmt.Println("Bad input:", parseErr.Num)
		}
	}
}

Run it:

go run wrapped-errors-demo.go

Observed output:

"8080": port=8080 range=false parse=false
"70000": port=0 range=true parse=false
"oops": port=0 range=false parse=true
Bad input: oops

Read the result

%w preserves the wrapped error for traversal. errors.Is finds the sentinel through the wrapper, while errors.As extracts the concrete *strconv.NumError. Both return false for a nil error. The invalid examples are handled deliberately, so the program still exits successfully.

Apply it carefully

Use error strings for explanation, not branch logic. Wrapping exposes an underlying error as part of your API contract; decide whether callers should depend on that identity. This helper checks a numeric port range only, not whether a service can bind the port.

The official Go reference documents the behavior used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.

rm -- wrapped-errors-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on