Chapter 7: Errors in Go
The introduction explains why software must handle unexpected conditions and previews Go’s explicit, flexible approach to errors through a small key/value database example.
After this chapter you will be able to
- Go’s explicit error approach. Describe Go’s preference for direct, explicit error handling over exception-handling abstractions, while noting that errors can be handled at different levels of depth.
- Error return value convention. Explain that a Go function can return an error value alongside ordinary results, conventionally as its final return value. Describe how an error return type makes a potentially failing function recognizable.
- Immediate error checking flow. Describe the common flow of calling a function and immediately checking whether err != nil. Explain that handling an error where it first appears is deliberately repetitive but keeps program flow predictable, rather than deferring it to a distant exception handler.
- Minimal error interface. Explain that Go’s error type is an interface with one method, Error(), which returns a message string. Describe how this small interface lets any type represent an error and carry additional information or behavior.
- Immediate error handling. Explain that a non-nil error value reports that an error happened, while Error() supplies its message. Describe the rule to handle a returned error promptly, whether by returning it, adding context, logging and exiting, retrying, or explicitly ignoring it.
- New error creation methods. Distinguish errors.New, which creates an error from a static string, from fmt.Errorf, which creates a formatted error that can convey more information.
- Errors as values and branches. Explain that an error is a value a function can check where it occurs, rather than an exception handled through try/catch; ignoring the returned error is possible but normally an antipattern.
- Logging and error propagation. Trace a common handling path: record useful information in a log, then return the existing error, a new error message, or a wrapped error to the caller, which can handle or pass it upward in turn.
- Basic error reporting packages. Explain that an application can report an error by printing it with either the standard library’s fmt or log package, and that log works similarly while adding newlines automatically.
- Context in error messages. Explain why an error may need more context as it moves through an application, and describe how to name the failed operation when logging, passing on, or generating it.
- Sentinel error identity and tradeoff. Explain that a sentinel error is a specific error value identifying a particular condition. Describe how its consistent identity supports error-handling decisions, while carrying less condition-specific data than other errors might.
- Package sentinels and standard examples. Describe defining sentinel errors for common conditions at the package level, conventionally with names starting with Err, and using them in place of existing errors. Give io.EOF as a standard-library example meaning no more data can be read, and explain that using established sentinels helps code work with other libraries.
- Sentinel error decisions. Explain how errors.Is lets code recognize a sentinel error and choose a response based on that result rather than merely returning its message.
- Wrapped error recognition. Explain that formatting an error with %w wraps it, and errors.Is can still find a matching error within a stack of wrapped errors.
- Custom error type design. Explain how a type based on a slice of strings can become a Go error by implementing Error() string, while retaining the missing keys as data.
- Bulk lookup error collection. Trace how the bulk-get method checks each requested key, collects keys that produce the missing-key sentinel error, and returns the custom error only when keys are missing.
- Underlying error type access. Explain how errors.As checks whether an error has a particular underlying concrete type or implements an interface, then makes the matching value available for examination.
- Pointer target for errors.As. Describe how to call errors.As: initialize a value of the desired concrete type or interface, pass a pointer to that value, and use the matched instance if the call returns true. Explain why a pointer-based error type commonly requires passing a pointer to a pointer.
- Panic as an emergency exit. Explain that a panic stops an application that cannot proceed, whether triggered by a runtime failure such as a nil reference or explicitly with Go’s panic function. Describe how the runtime prints a message with the reason at the top.
- Uninitialized store panic message. Trace the key-value store example: constructing KVStore directly rather than through NewKVStore leaves its underlying map uninitialized, and an explicit panic provides the clearer message “uninitialized kv store” instead of leaving users to decipher a nil error.
- Deferred panic recovery. Explain how defer and recover work together to handle a panic, including what recover returns when a panic occurs or does not occur. Describe the section’s SafeKeys wrapper around KVStore.Keys() and the stated exception for panic(nil).
- Choosing errors over panic. Explain the questions to ask before calling panic: whether it could affect users of the code, whether the entire program truly must terminate immediately, and whether the process has no reasonable way to recover from an inconsistent state. Describe why returning an error is usually the better choice.
- Recover as a stopgap. Explain that recover may temporarily help with sporadic panics in code from elsewhere, but the preferred response is to find and fix the cause, ask the code's author to address it, or change how the code is used or replace it.
What it builds on
- Multiple return values (chapter 2) · Introduces functions with multiple return values.
- Basic error handling and the log package (chapter 2) · Introduces basic error handling.
- Interfaces (chapter 5) · The meaning of an interface and its methods.
- Pointer types (chapter 3) · Pointer types and pointers to values.
- Comments (chapter 2) · Using comments in Go code
Section by section
Introduction
The introduction explains why software must handle unexpected conditions and previews Go’s explicit, flexible approach to errors through a small key/value database example.
- Need for error handling: Explain why even well-written software must handle unexpected conditions outside its control.
Why it matters: Handling conditions you cannot prevent helps make software more resilient. - Go’s explicit error approach: Describe Go’s preference for direct, explicit error handling over exception-handling abstractions, while noting that errors can be handled at different levels of depth.
Why it matters: This approach frames how the chapter handles errors throughout. - Chapter topics and example: Describe the chapter’s planned topics—the error type, handling errors, distinguishing error types, custom errors, and panic and recover—and identify the small key/value database used to demonstrate them.
Why it matters: The roadmap connects the chapter’s error-handling techniques to a concrete example.
Key terms: unexpected conditions, error handling, directness, explicitness, exception handling, error type, custom error types, panic, recover, key/value database
7.1 Go errors are values
This section introduces Go’s practice of returning errors as values and checking them immediately where they occur.
- Error return value convention: Explain that a Go function can return an error value alongside ordinary results, conventionally as its final return value. Describe how an error return type makes a potentially failing function recognizable.
Why it matters: Recognizing error return values lets you see where a Go program needs to account for failure. - Immediate error checking flow: Describe the common flow of calling a function and immediately checking whether err != nil. Explain that handling an error where it first appears is deliberately repetitive but keeps program flow predictable, rather than deferring it to a distant exception handler.
Why it matters: The check determines what the program does after a call fails, close to the source of the failure. - Scoped error check shorthand: Describe how a call to a function that returns only an error can be placed in an if statement so its returned value is available for the check and handling only within that statement.
Why it matters: This shorthand keeps a common error-only call and its handling together.
Key terms: error type, error value, return values, if err != nil, nil, error-handling flow, if statement
7.2 The Go error type
This section defines Go’s minimal error interface and emphasizes handling errors as soon as a function returns them.
- Minimal error interface: Explain that Go’s error type is an interface with one method, Error(), which returns a message string. Describe how this small interface lets any type represent an error and carry additional information or behavior.
Why it matters: The small interface explains both what every error provides and why richer error types are possible. - Immediate error handling: Explain that a non-nil error value reports that an error happened, while Error() supplies its message. Describe the rule to handle a returned error promptly, whether by returning it, adding context, logging and exiting, retrying, or explicitly ignoring it.
Why it matters: Checking results promptly keeps failures from being overlooked and lets code choose an appropriate response.
Key terms: error, interface, Error(), nil, error check, annotating, handle your results as soon as you get them
7.3 Generating new errors
This section compares two ways to create Go errors and shows how placeholder errors can support an interface before its implementation is ready.
- New error creation methods: Distinguish errors.New, which creates an error from a static string, from fmt.Errorf, which creates a formatted error that can convey more information.
Why it matters: Choosing an informative error message helps callers understand what went wrong. - Placeholder errors for unfinished implementations: Describe using errors as placeholders while assembling a Store interface and its definitions before the implementation is ready.
Why it matters: Placeholders let you put an interface-based design together before its behavior is complete.
Key terms: errors.New, fmt.Errorf, static string, formatted errors, Store interface, placeholders
7.4 Handling errors
This section explains why Go errors call for an explicit decision where they occur, how errors can be logged and passed to callers, and how an application may ultimately exit.
- Errors as values and branches: Explain that an error is a value a function can check where it occurs, rather than an exception handled through try/catch; ignoring the returned error is possible but normally an antipattern.
Why it matters: Treating an error as a value makes the error-handling branch an explicit part of the code. - Logging and error propagation: Trace a common handling path: record useful information in a log, then return the existing error, a new error message, or a wrapped error to the caller, which can handle or pass it upward in turn.
Why it matters: Useful logs and propagation help later code and people diagnosing the failure find the information they need. - Application exit and panic: Distinguish a graceful exit using os.Exit(int), where zero signals normal exit and a nonzero value signals a problem, from panic(any), which can exit from the current function while outputting a message or error and a code path.
Why it matters: These exit paths communicate a failure differently and should not be confused with ordinary error propagation.
Key terms: errors as values, try/catch, antipattern, log, calling function, wrapping an error, os.Exit(int), panic(any), panic(nil)
7.5 Logging errors
This section shows how to report errors with Go’s fmt and log packages and how log adds configurable information to messages.
- Basic error reporting packages: Explain that an application can report an error by printing it with either the standard library’s fmt or log package, and that log works similarly while adding newlines automatically.
Why it matters: This gives you a straightforward way to make an error visible when it occurs. - Log message metadata flags: Describe how log adds a timestamp to messages by default and how its flags can add information such as a filename and line number or be cleared with log.SetFlags(0).
Why it matters: Log flags let you choose how much context appears alongside each reported error.
Key terms: fmt, log, standard library, timestamp, flags, log.SetFlags(0)
7.6 Annotating error logs with formatting
The section shows how to make ambiguous Go errors more useful by adding context when logging them with formatted text.
- Context in error messages: Explain why an error may need more context as it moves through an application, and describe how to name the failed operation when logging, passing on, or generating it.
Why it matters: The added context tells you where to start looking even when the original error message is vague. - Formatted error output: Describe how %s and %v print a Go error, including their small difference when the error might be nil.
Why it matters: You can choose a format verb without losing the error’s message. - Structured logging alternative: State that structured logging can attach separate fields describing circumstances around an error, and identify slog as the package mentioned for it.
Why it matters: Separate fields are another way to preserve the circumstances of a failure.
Key terms: annotating errors, context, %s, %v, Error(), structured logging, slog
7.7 Sentinel errors
This section introduces sentinel errors as stable, recognizable error values for common conditions and shows why they help error-handling code.
- Sentinel error identity and tradeoff: Explain that a sentinel error is a specific error value identifying a particular condition. Describe how its consistent identity supports error-handling decisions, while carrying less condition-specific data than other errors might.
Why it matters: Recognizing a sentinel lets you handle a known condition consistently rather than relying on a changing error message. - Package sentinels and standard examples: Describe defining sentinel errors for common conditions at the package level, conventionally with names starting with Err, and using them in place of existing errors. Give io.EOF as a standard-library example meaning no more data can be read, and explain that using established sentinels helps code work with other libraries.
Why it matters: Using the same named errors across code and libraries makes common failure conditions easier to recognize and handle.
Key terms: sentinel error, error value, identity, Err, package level, io.EOF
7.8 Identifying different types of errors
This section shows how to recognize sentinel errors with errors.Is, including when they are wrapped, so code can respond to specific failures.
- Sentinel error decisions: Explain how errors.Is lets code recognize a sentinel error and choose a response based on that result rather than merely returning its message.
Why it matters: Recognizing a particular error lets your program handle that case deliberately. - Wrapped error recognition: Explain that formatting an error with %w wraps it, and errors.Is can still find a matching error within a stack of wrapped errors.
Why it matters: Wrapping can add context without preventing callers from recognizing the original error. - Information beyond error messages: Describe why an error value can carry information beyond its message: error is an interface that can contain different types, and the errors package provides errors.Is and errors.As for exploring errors programmatically.
Why it matters: An error can provide more to work with than its displayed text.
Key terms: errors.Is(), sentinel errors, %w, wrap errors, error interface, errors are values, errors.As()
7.9 Creating a custom error type
This section builds a custom Go error that records missing keys during a bulk lookup and explains why an error type can carry more than a message.
- Custom error type design: Explain how a type based on a slice of strings can become a Go error by implementing Error() string, while retaining the missing keys as data.
Why it matters: A custom error can carry useful details through Go’s ordinary error interface. - Bulk lookup error collection: Trace how the bulk-get method checks each requested key, collects keys that produce the missing-key sentinel error, and returns the custom error only when keys are missing.
Why it matters: This shows how one error can report details gathered across several operations.
Key terms: custom error type, error interface, Error() string, slice of strings, bulk get, sentinel error, missing keys, nil
7.10 Getting the underlying error with errors.As
This section shows how to use errors.As to recover a specific underlying error type and act on its task-specific data.
- Underlying error type access: Explain how errors.As checks whether an error has a particular underlying concrete type or implements an interface, then makes the matching value available for examination.
Why it matters: The specific error type can carry useful information that is not available through the error interface alone. - Pointer target for errors.As: Describe how to call errors.As: initialize a value of the desired concrete type or interface, pass a pointer to that value, and use the matched instance if the call returns true. Explain why a pointer-based error type commonly requires passing a pointer to a pointer.
Why it matters: The pointer-to-pointer requirement is easy to miss when trying to inspect a typed error. - BulkGet error recovery example: Describe how the main function uses errors.As on a BulkGet error to obtain its underlying list of keys and add those keys to the store as placeholders. Explain that this lets errors flow through the system until a caller is ready to handle their data.
Why it matters: The example connects typed error inspection to a concrete recovery action. - Finding candidate error types: Describe using package godoc to identify exported error types worth examining with errors.As, noting the common Error suffix and the need to read what each call may return.
Why it matters: Documentation helps you choose a type to look for rather than guessing.
Key terms: errors.As, error interface, concrete type, interface, pointer to a pointer, BulkGet, keys, placeholders, godoc, exported error type
7.11 An unexpected error journey
This section explains when a Go program may exit through a panic and uses an uninitialized key-value store to show how an explicit panic can give a clearer message than a nil-reference failure.
- Panic as an emergency exit: Explain that a panic stops an application that cannot proceed, whether triggered by a runtime failure such as a nil reference or explicitly with Go’s panic function. Describe how the runtime prints a message with the reason at the top.
Why it matters: This distinction helps you recognize both accidental panics and deliberate ones. - Restarting for system resilience: Justify why an application that cannot connect to a required external service might exit and restart instead of repeatedly trying to reconnect: in a clustered system, it may then be moved to a node that can connect.
Why it matters: An application exit can sometimes help the larger system recover rather than simply represent a failure. - Uninitialized store panic message: Trace the key-value store example: constructing KVStore directly rather than through NewKVStore leaves its underlying map uninitialized, and an explicit panic provides the clearer message “uninitialized kv store” instead of leaving users to decipher a nil error.
Why it matters: The example shows why a deliberate failure message can be more useful than an unexplained nil panic.
Key terms: panic, nil reference, panic function, external service, clustered systems, self-healing, KVStore, NewKVStore, Keys, uninitialized kv store
7.12 Don’t panic
This section explains when panic recovery is worth attempting and introduces a deferred recover call that lets a wrapper handle a panic from KVStore.Keys().
- Deferred panic recovery: Explain how defer and recover work together to handle a panic, including what recover returns when a panic occurs or does not occur. Describe the section’s SafeKeys wrapper around KVStore.Keys() and the stated exception for panic(nil).
Why it matters: A deferred cleanup action alone does not handle a panic; recovery requires a deferred call to recover. - Reasons to attempt recovery: Justify attempting recovery in applications where an unexpected panic could cost data, uptime, or connections, while distinguishing those applications from ones that can exit and restart quickly.
Why it matters: Whether to recover depends on what the application stands to lose by exiting.
Key terms: defer, recover, panic(nil), SafeKeys, KVStore, Keys(), panic, recovery, uptime, durability
7.12.1 Using Panic and Recover in your code
This subsection explains why Go code should usually return errors instead of panicking, when a panic might be justified, and why recover is usually a temporary workaround.
- Choosing errors over panic: Explain the questions to ask before calling panic: whether it could affect users of the code, whether the entire program truly must terminate immediately, and whether the process has no reasonable way to recover from an inconsistent state. Describe why returning an error is usually the better choice.
Why it matters: These questions help you avoid terminating a program for a failure its caller could handle. - Rare panics and documentation: Describe the kind of rare, externally caused failure for which a panic might remain in code, and state that a function that can panic should say so in a comment for generated godoc because the panic is otherwise hidden from callers.
Why it matters: Documenting a possible panic gives callers warning they cannot get from the function signature. - Recover as a stopgap: Explain that recover may temporarily help with sporadic panics in code from elsewhere, but the preferred response is to find and fix the cause, ask the code's author to address it, or change how the code is used or replace it.
Why it matters: Catching a panic does not remove the underlying defect.
Key terms: panic, error, inconsistent state, godoc, recover
7.13 Summary
The chapter summary recaps how Go errors are represented, handled and identified, and why panics are not a substitute for ordinary errors.
Where this is used later
- Writing tests for error conditions (chapter 8) · Applies error conditions in tests.
- Writing good test logs (chapter 8) · Writing useful test logs draws on the practice of making failures clear.
Concepts explained in this chapter
- error typeAn error type is Go's interface type for representing failed or abnormal operation results through an Error method.
- failureAn error is a value or condition indicating that an operation failed or cannot produce its intended result.
- error valueAn error value is a Go value of the error type that reports whether an operation encountered a problem.
- sentinel errorA sentinel error is a predefined error value identified by comparison so callers can make programmatic decisions about a failure condition.
- panicA panic is Go's runtime mechanism for terminating ordinary execution and reporting an unrecoverable run-time error unless recovered.
- errors.Aserrors.As is a Go standard-library function that finds an error in an error chain assignable to a specified type and stores it in a destination value.
- GoGo is an open-source, statically typed programming language designed for efficient compilation, execution, concurrency, and software development tooling.
- interfaceAn interface is a contract specifying operations or type-set membership that a type can provide without defining the type's implementation.
- format verbA format verb is a formatting directive that specifies how a value is converted to text in formatted output.
- fmtThe fmt package is a Go standard-library package that provides formatted input and output operations.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
