Wrapping Errors
return fmt.Errorf("context: %w", err)
errors.Unwrap(err)
Wrapping with %w
fmt.Errorf's %w verb, used in place of %v or %s, wraps the given error inside a new one, adding a message prefix while preserving the ability to unwrap back to the original.
Example: Wrapping with %w
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"errors"
"fmt"
)
// Define the function readConfig
func readConfig() error {
// Send errors.New("file missing") back to the caller
return errors.New("file missing")
}
// Define the function startApp
func startApp() error {
// Check whether err := readConfig(); err != nil
if err := readConfig(); err != nil {
return fmt.Errorf("startApp: %w", err)
}
// Send nil back to the caller
return nil
}
// main is where the program starts running
func main() {
fmt.Println(startApp())
}
Login to try C/C++/Java/PHP code in the editor
Checking Through a Wrapped Chain
Even after several layers of wrapping, errors.Is can still detect the original error at the bottom of the chain, letting each layer add context without breaking error identity checks further up the call stack.
Example: Checking Through a Wrapped Chain
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"errors"
"fmt"
)
var ErrConfigMissing = errors.New("config missing")
// Define the function readConfig
func readConfig() error {
// Send ErrConfigMissing back to the caller
return ErrConfigMissing
}
// Define the function startApp
func startApp() error {
// Check whether err := readConfig(); err != nil
if err := readConfig(); err != nil {
return fmt.Errorf("startApp failed: %w", err)
}
// Send nil back to the caller
return nil
}
// main is where the program starts running
func main() {
err := startApp()
fmt.Println(err)
fmt.Println("is config missing:", errors.Is(err, ErrConfigMissing))
}
Login to try C/C++/Java/PHP code in the editor
Manually Unwrapping
errors.Unwrap retrieves the single error wrapped one level down, which is the primitive that errors.Is and errors.As use internally to walk the whole chain step by step.
Example: Manually Unwrapping
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"errors"
"fmt"
)
// main is where the program starts running
func main() {
// Short declaration: create base and infer the type
base := errors.New("disk full")
// Short declaration: create wrapped and infer the type
wrapped := fmt.Errorf("save failed: %w", base)
inner := errors.Unwrap(wrapped)
// Print the values followed by a newline
fmt.Println("outer:", wrapped)
// Print the values followed by a newline
fmt.Println("inner:", inner)
}
Login to try C/C++/Java/PHP code in the editor
- Using %v instead of %w in fmt.Errorf, which loses the ability for errors.Is/errors.As to see the original wrapped error.
- Wrapping the same error many times through several layers, creating an error message so long it becomes unreadable.
- Forgetting that wrapping doesn't change what errors.Is/errors.As see -- they still find the originally wrapped sentinel or type.
- fmt.Errorf's %w verb wraps an existing error while adding additional context.
- A wrapped error still satisfies errors.Is/errors.As checks against the original error.
- Wrapping preserves the full chain of context as an error travels up through several function calls.
- Errors.Unwrap can manually retrieve the next error in a wrap chain, though Is/As are usually preferred.
Chapter Quiz — Complete all 6 topics to unlock
0/6 topics done
Complete these topics first: