The stdlib "errors" package in Go supports joining multiple errors in the addition to more commin %w wrapping.
Joining errors
You can join multiple errors in two ways. They have slightly different semantics under the hood (look at the Appendix section if you care), but they both work in a similar way.
1. By using multiple %w verbs on the same error
Go
var (
ErrRelayOrientation = errors.New("bad relay orientation")
ErrCosmicRayBitflip = errors.New("cosmic ray bitflip")
ErrStickyPlunger = errors.New("sticky sensor plunger")
)
err1 := fmt.Errorf("G-switch failed: %w %w %w", ErrRelayOrientation, ErrCosmicRayBitflip, ErrStickyPlunger)
// 2009/11/10 23:00:00 G-switch failed: bad relay orientation cosmic ray bitflip sticky sensor plunger
log.Fatal(err1)
2. The second one uses the errors.Join function introduced in Go 1.20.
The function takes in a variadic error argument, discards any nil values, and joins the rest of the provided errors. The message is formatted by joining the strings obtained by calling each argument’s Error() method, separated by a newline.
Go
err2 := errors.Join(
ErrRelayOrientation,
ErrCosmicRayBitflip,
ErrStickyPlunger,
)
// 2009/11/10 23:00:00 bad relay orientation
// cosmic ray bitflip
// sticky sensor plunger
log.Fatal(err2)
How to use them?
By now, we’ve seen the two ways that Go supports error wrapping, direct wrapping and joined errors.
Both variants ultimately form a tree of errors. The most common ways to inspect that tree are the errors.Is and errors.As functions. Both of these examine the tree in a pre-order, depth-first traversal by successively unwrapping every node found.
Go
func Is(err, target error) bool
func As(err error, target any) bool
The errors.Is function examines the input error’s tree, looking for a leaf that matches the target argument and reports if it finds a match. In our case, this can look for a leaf node that matches a specific joined error.
Go
ok := errors.Is(err1, ErrStickyPlunger)
fmt.Println(ok) // true
On the other hand, errors.As examines the input error’s tree, looking for a leaf that can be assigned to the type of the target argument. Think of it as an analog to json.Unmarshal.
Go
var engineErr *EngineError
ok = errors.As(err2, &engineErr)
fmt.Println(ok) // false
So, to summarize:
- errors.Is checks if a specific error is part of the error tree
- errors.As checks if the error tree contains an error that can be assigned to a target type
The catch
So far so good! We can use these two types of error wrapping to form a tree. But let’s say we wanted to inspect that tree in a more manual way on another part of the codebase. The errors.Unwrap function allows you to get direct wrapped errors.
But there’s a slight complication here. Let’s try to call errors.Unwrap() directly on any of the two joined errors created above.
Go
fmt.Println(errors.Unwrap(err1)) // nil
fmt.Println(errors.Unwrap(err2)) // nil
So, why nil?! What’s going on? How can I get the original errors slice and inspect it? Turns out, that the two ‘varieties’ implement a different Unwrap method.
Go
Unwrap() error
Unwrap() []error
The documentation of errors.Unwrap method clearly states that it only calls the first one and does not unwrap errors returned by Join. There have been multiple discussions on golang/go about allowing a more straightforward way to unwrap joined errors, but there has been no consensus.
The way to achieve it right now is to either use errors.As or an inline interface cast to get access to the second Unwrap implementation.
Go
var joinedErrors interface{ Unwrap() []error }
// You can use errors.As to make sure that the alternate Unwrap() implementation is available
if errors.As(err1, &joinedErrors) {
for _, e := range joinedErrors.Unwrap() {
fmt.Println("-", e)
}
}
// Or do it more directly with an inline cast
if uw, ok := err2.(interface{ Unwrap() []error }); ok {
for _, e := range uw.Unwrap() {
fmt.Println("~", e)
}
}
So, it’s an extra little step, but with either of these techniques you’ll be able to retrieve the original slice of errors and manually traverse the error tree.
Similar Reads
Go Error Handling In Go, error handling is done by returning error values instead of using try-catch like in Java or Python. This approach ensures explicit error handling, improving code clarity and control.The error type in Go is an interface with a single method:type error interface { Error() string}Any type implem
5 min read
strings.Join() Function in Golang With Examples strings.Join() Function in Golang concatenates all the elements present in the slice of string into a single string. This function is available in the string package. Syntax: func Join(s []string, sep string) string Here, s is the string from which we can concatenate elements and sep is the separato
1 min read
Interesting Facts About Golang Go (also known as Golang or Go language) is the language developed by Google. Go is an open-source, statically-typed compiled, and explicit programming language. According to Google Developers, Go is a dependable and efficient programming language. Go supports Concurrent programming. Go is also a mu
2 min read
Object-Oriented Programming in GoLang Object-oriented programming is a programming paradigm which uses the idea of "objects" to represent data and methods. Go does not strictly support object orientation but is a lightweight object Oriented language. Object Oriented Programming in Golang is different from that in other languages like C+
3 min read
Custom Errors in Go Go handles errors using explicit error values instead of exceptions. The error type is an interface with a single method:type error interface { Error() string}Any type implementing Error() can be used as an error, allowing developers to create custom errors with meaningful context. This guide covers
7 min read
Import in GoLang Pre-requisite learning: Installing Go on Windows / Installing Go on MacOSÂ Technically defining, a package is essentially a container of source code for some specific purpose. This means that the package is a capsule that holds multiple elements of drug/medicine binding them all together and protect
9 min read
ring.Link() Function in Golang With Examples ring.Link() function in Golang is used to connect two ring r and ring s ring.next() function connect last node of ring r to first node of ring s .so that for r.next() must not be empty. Otherwise, it will throw an error. It works like a circular linked list. Syntax: func (r *Ring) Link(s *Ring) *Rin
2 min read
How to Split a String in Golang? In Go language, strings differ from other languages like Java, C++, and Python. A string in Go is a sequence of variable-width characters, with each character represented by one or more bytes using UTF-8 encoding. In Go, you can split a string into a slice using several functions provided in the str
3 min read
Channel in Golang In Go language, a channel is a medium through which a goroutine communicates with another goroutine and this communication is lock-free. Or in other words, a channel is a technique which allows to let one goroutine to send data to another goroutine. By default channel is bidirectional, means the gor
7 min read
Methods in Golang Go methods are like functions but with a key difference: they have a receiver argument, which allows access to the receiver's properties. The receiver can be a struct or non-struct type, but both must be in the same package. Methods cannot be created for types defined in other packages, including bu
3 min read