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
ruby
class TemplateInterpolator PLACEHOLDER = /\{\{\s*([\w.]+)\s*\}\}/ def initialize(strict: false)
Interpolating templates with dotted keys in Ruby
regex
string-interpolation
hash-traversal
Intermediate
6 steps
go
package handlers import ( "net/http"
Serving embedded static meta files in Gin
embedding
static assets
http caching
Intermediate
6 steps
go
package config import ( "fmt"
A thread-safe config singleton in Go
singleton
concurrency
environment-variables
Intermediate
7 steps
rust
use std::net::Ipv4Addr; use std::str::FromStr; #[derive(Debug, Clone, Copy)]
Parsing and matching IPv4 CIDR ranges in Rust
bitwise-operations
parsing
error-handling
Intermediate
8 steps
go
package scheduler import ( "container/heap"
A priority job queue with Go's container/heap
priority-queue
heap
interfaces
Intermediate
9 steps
typescript
import { parsePhoneNumberFromString, CountryCode } from 'libphonenumber-js'; export interface NormalizedPhone { e164: string;
Normalizing phone numbers to E.164 in TypeScript
validation
normalization
error-handling
Intermediate
7 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.