liveBook · Manning

Failure

As explained in Learn Go with Pocket-Sized Projects

An error as a value is an explicit value representing an operation failure that code returns and handles directly.

Go treats an operation failure as a value to return and handle, rather than an exception. A function can return both its result and an error; the caller checks the error before using the result. This makes the path where something goes wrong explicit. section 1.3.1 section 5.3.2

Read a word corpus

ReadCorpus(path) returns ([]string, error). If os.ReadFile fails, it returns nil and wraps the failure with the path: fmt.Errorf("unable to open %q for reading: %w", path, err). An empty file returns nil and ErrCorpusIsEmpty; a successful read returns the words and nil. Tests check both the word count and the error. section 5.3.2

A recognizable failure can be a sentinel error. ErrCorpusIsEmpty is a constant of a custom string type whose Error() method implements the error interface. This gives callers a stable value to check without exposing a mutable global error variable. Wrapping with %w preserves an underlying error for inspection. section 5.3.2 section 6.2.4

Choose the check that answers the caller’s question:

  • Use errors.Is to recognize a particular failure, such as ErrTimeout or ErrInvalidDecimal, including a wrapped error. section 6.6.2 section 6.2.4
  • Use errors.As when behavior depends on an error’s type. For an HTTP request failure, extract *url.Error and call its Timeout() method. section 6.6.2

Failure handling also shapes the boundary presented to clients. When an endpoint normally returns JSON, format its errors as JSON too—for example, {"error":"game over"}—so clients can use the same decoder regardless of response status. section 8.6.4

Listing 5.32 corpus.go: ReadCorpus method
const ErrCorpusIsEmpty = corpusError("corpus is empty")

// ReadCorpus reads the file located at the given path
// and returns a list of words.
func ReadCorpus(path string) ([]string, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("unable to open %q for reading: %w", 
            path, err)
    }

    if len(data) == 0 {
        return nil, ErrCorpusIsEmpty
    }

    // we expect the corpus to be a line- or space-separated 
    // list of words
    words := strings.Fields(string(data))

    return words, nil
}

Follow each return in ReadCorpus to see how an ordinary read failure, a recognizable empty-corpus failure, and success produce different result–error pairs. section 5.3.2

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through result–error returns, custom sentinels, wrapping, and errors.Is and errors.As checks; does not explore error-interface internals.
Examples
Uses a word-corpus reader, decimal parser, HTTP timeout test, and JSON error response.
What it assumes
Assumes familiarity with Go functions and tests while introducing custom error-interface implementations and typed error inspection through examples.
What you can do after
Build it from scratch with explicit result–error returns, recognizable sentinels, and checks for specific failures.
How it connects
Connects error handling to the error interface, sentinel errors, error wrapping, HTTP timeouts, unit testing, and JSON responses.

Also explained in

Compare how 12 books explain failure →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.