Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
149: errors.Is and errors.As
Imagine you're sorting through a stack of returned mail. Some envelopes are plain, but some are wrapped in three layers of protective packaging—bubble wrap, a cardboard box, and then a plastic mailer. If you're looking for a specific "Return to Sender" stamp from the post office, you don't just look at the outermost plastic mailer and give up if you don't see the stamp. You peel back the layers until you find the original envelope inside. If that stamp is anywhere in the chain, the package is a return.
That's exactly how errors.Is and errors.As work in Go. Since Go 1.13, we've been "wrapping" errors to add context (using fmt.Errorf("...: %w", err)). Because of this, a simple if err == sql.ErrNoRows often fails because the error isn't sql.ErrNoRows anymore—it's a wrapper containing sql.ErrNoRows.
Stop comparing errors with the double-equals
I've seen countless bugs where developers use == to check for a sentinel error, only to find that their error handling logic stopped working the moment they added a bit of context to their logs. When you wrap an error, you're creating a new error value. The original is still there, but it's hidden inside.
errors.Is is your way of peeling back those layers. It recursively unwraps the error chain until it finds a match or hits the end. Here is how you should be doing it when dealing with sentinel errors (those predefined error variables):
package main
import (
"errors"
"fmt"
"os"
)
var ErrDiskFull = errors.New("disk is full")
func saveFile() error {
// We wrap the sentinel error to provide more context
return fmt.Errorf("failed to save user profile: %w", ErrDiskFull)
}
func main() {
err := saveFile()
// This would be FALSE because err is a wrapped error, not ErrDiskFull itself
if err == ErrDiskFull {
fmt.Println("Direct match found!")
}
// This is TRUE because it searches the entire wrap chain
if errors.Is(err, ErrDiskFull) {
fmt.Println("Found the disk full error deep in the chain!")
}
}
Extracting data with errors.As
Now, sometimes you don't just want to know if an error happened; you need to get your hands on the data inside the error. Maybe you have a custom error struct that contains an HTTP status code or a field name that failed validation. Using a type assertion (like err.(*MyError)) is risky because, again, the error might be wrapped.
errors.As is like saying: "Search through this chain of errors. If you find one that matches this specific type, assign it to my variable and let me know."
package main
import (
"errors"
"fmt"
)
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query %q failed: %v", e.Query, e.Err)
}
func runQuery() error {
qErr := &QueryError{Query: "SELECT * FROM users", Err: errors.New("timeout")}
return fmt.Errorf("database layer: %w", qErr)
}
func main() {
err := runQuery()
var target *QueryError
// This searches the chain for anything that can be assigned to *QueryError
if errors.As(err, &target) {
fmt.Printf("The failing query was: %s\n", target.Query)
}
}
Notice that I passed &target (a pointer to a pointer) into errors.As. This is a common point of confusion. The function needs to be able to modify your target variable to point to the actual error found in the chain, which is why it requires a pointer to that variable.
When to use which?
It's a simple rule of thumb I use: Use errors.Is for "Which one is this?" (comparing values). Use errors.As for "What is this?" (checking types and extracting data). If you find yourself writing if err != nil followed by a type assertion, you should probably be using errors.As instead.
📋 Practical Task
Implementing a Robust API Rate Limit Handler
You are building a middleware for an API. You need to handle two types of failures: a generic "Service Unavailable" sentinel error and a specific "RateLimitError" struct that contains the time the user can try again.
Requirements:
- Create a sentinel error named
ErrServiceUnavailable. - Create a struct
RateLimitErrorwith a fieldRetryAfter int(representing seconds). Ensure it implements theerrorinterface. - Write a function
SimulateRequest() errorthat randomly returns one of the two errors, but wraps them usingfmt.Errorfand the%wverb to simulate a call stack. - In
main, callSimulateRequest()and useerrors.Isto detect the service unavailability anderrors.Asto extract theRetryAftervalue from the rate limit error. - Print a specific message for each case: "Service is down, try later" or "Rate limited! Please wait X seconds."
There are no comments for now.