go
53 lines · 8 steps
Wrapping and inspecting errors in Go
How sentinel errors, custom error types, and wrapping let callers react to failures precisely.
Explained by
highlit
1package store
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7)
8
9var ErrNotFound = errors.New("record not found")
10
11type QueryError struct {
12 Query string
13 Err error
14}
15
16func (e *QueryError) Error() string {
17 return fmt.Sprintf("query %q failed: %v", e.Query, e.Err)
18}
19
20func (e *QueryError) Unwrap() error {
21 return e.Err
22}
23
24func (s *Store) FindUser(id int64) (*User, error) {
25 const query = "SELECT id, email FROM users WHERE id = $1"
26
27 var u User
28 err := s.db.QueryRow(query, id).Scan(&u.ID, &u.Email)
29 if err != nil {
30 if errors.Is(err, sql.ErrNoRows) {
31 return nil, fmt.Errorf("user %d: %w", id, ErrNotFound)
32 }
33 return nil, &QueryError{Query: query, Err: err}
34 }
35
36 return &u, nil
37}
38
39func (s *Store) ResolveUser(id int64) (*User, error) {
40 u, err := s.FindUser(id)
41 if err != nil {
42 if errors.Is(err, ErrNotFound) {
43 return s.guestUser(), nil
44 }
45
46 var qe *QueryError
47 if errors.As(err, &qe) {
48 s.log.Printf("db failure on %q: %v", qe.Query, qe.Err)
49 }
50 return nil, err
51 }
52 return u, nil
53}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Wrapping an error with %w preserves the original so callers can match it later with errors.Is or errors.As.
- 2Implementing Unwrap on a custom error type plugs it into Go's standard error-inspection chain.
- 3Distinguishing 'not found' from 'real failure' lets callers degrade gracefully instead of treating every error the same.
Related explainers
go
package main import ( "errors"
Parsing and validating CLI flags in Go
cli-parsing
validation
error-handling
Intermediate
8 steps
java
public class ThumbnailProcessor { private static final int MAX_CONCURRENCY = 4;
Bounded parallel thumbnail rendering in Java
concurrency
thread-pool
futures
Intermediate
7 steps
go
package cache import ( "container/list"
Building a generic LRU cache in Go
lru-cache
generics
linked-list
Intermediate
8 steps
go
package model import ( "encoding/json"
Custom JSON marshaling in Go
json
serialization
interfaces
Intermediate
5 steps
go
func (h *TransactionHandler) ExportCSV(c *gin.Context) { ctx := c.Request.Context() filters := parseTransactionFilters(c)
Streaming a CSV export in Gin
streaming
csv-export
database-cursor
Intermediate
8 steps
php
<?php namespace App\Rules;
How a custom phone validation rule works in Laravel
validation
custom-rules
dependency
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/wrapping-and-inspecting-errors-in-go-explained-go-0b02/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.