Skip to content
tezvyn:

Go Error Wrapping: Preserving Context, Not Just Text

Source: go.devMediumHow cards are made

Go's error wrapping adds context without losing the original error's type. Use fmt.Errorf with %w to create a chain of errors, then inspect it with errors.Is or errors.As. The footgun is using %v, which just formats the error as a string.

Why it exists

Before Go 1.13, adding context to an error often meant creating a new one with fmt.Errorf("... %v", err). This converted the original error into a string, discarding its valuable type information. Code couldn't programmatically inspect the original cause, only read a combined message.

The mental model

Think of error wrapping as a set of nested boxes. The innermost box is the original error, like "network connection refused". Each function that handles this error can put it inside a slightly larger box with a new label, like "failed to fetch user data". This creates a chain of context. You can open all the boxes to see the full story, from the high-level failure down to the root cause.

How it works

Go 1.13 introduced a standard way to handle this. An error can "wrap" another by implementing an Unwrap() error method. The fmt.Errorf function now has a %w verb that creates a wrapped error for you. To inspect the chain, you don't call Unwrap directly. Instead, you use errors.Is(err, target) to check if any error in the chain matches a specific sentinel value (like io.EOF), or errors.As(err, &target) to find and assign an error of a specific type.

When to use it

Use error wrapping whenever you return an error from a function and want to add context about what your function was trying to do. For example, a ReadConfig function might encounter an os.ErrNotExist but should return an error like "failed to read config: file does not exist". Wrapping preserves the original os.ErrNotExist so the caller can specifically check for it.

When not to use it

Don't wrap an error if the lower-level error is an implementation detail that callers should not depend on. If you are translating an error into a different one for abstraction purposes, like turning any database error into a generic ErrRepositoryUnavailable, you might create a new error without wrapping. This prevents callers from coupling their logic to the specific database driver's error types.

One canonical example

Before Go 1.13, you might write return fmt.Errorf("could not decompress: %v", err). This loses the original error's type; the caller only sees a string. With Go 1.13, you write return fmt.Errorf("could not decompress: %w", err). Now, the caller can use errors.Is(returnedErr, specificErrType) to see if the failure was caused by a specific underlying problem, while still having the "could not decompress" context message.

Interview question

When adding context to an error using fmt.Errorf, what is the primary consequence of using the %v verb instead of %w?

  • a.The resulting error will automatically unwrap itself when passed to errors.Is or errors.As, behaving identically to %w.
  • b.The original error's type is preserved, but errors.Is and errors.As cannot be used with the wrapped error.
  • c.The program will fail to compile because %v is not a valid verb for error formatting.
  • d.The new error will only contain the string representation of the original error, making its underlying type uninspectable.Correct
Why?

Using %v with fmt.Errorf converts the original error into its string representation, discarding its valuable type information. This prevents programmatic inspection of the underlying error using functions like errors.Is or errors.As. Option B is incorrect because if the type were preserved, errors.Is and errors.As would be usable.

Just read this? Test yourself on what you have been reading.

Read the original → go.dev

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on go — each one lists the topics its interview covers.

See open roles