Error Wrapping (fmt.Errorf)
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 54 of 107.
When errors pass through multiple layers of your application, knowing where an error originated becomes crucial. Go's fmt.Errorf function with the %w verb lets you wrap errors, preserving the original error while adding context.
Error wrapping creates a chain of errors. Each layer can add information about what it was trying to do when the error occurred:
func ReadConfig(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("reading config file: %w", err)
}
// process data...
return nil
}The %w verb is special—it wraps the original error inside the new one. This is different from %v, which just converts the error to a string and loses the original error's identity.
func LoadSettings() error {
err := ReadConfig("settings.json")
if err != nil {
return fmt.Errorf("loading settings: %w", err)
}
return nil
}When LoadSettings fails, the error message shows the full chain: "loading settings: reading config file: open settings.json: no such file or directory". Each layer adds context, making debugging much easier.
The wrapped error still contains the original error inside it. In the next lesson, you'll learn how to unwrap these errors and check what's inside using errors.Is() and errors.As().
Challenge
EasyLet's build an order processing system that demonstrates error wrapping across multiple layers. You'll create a chain of functions where each layer adds context to errors, making it easy to trace exactly where problems occur.
You'll organize your code across two files:
orders.go: Create the order processing logic with multiple layers that wrap errors as they propagate upward.Implement three functions that form a processing chain:
ValidateItem(itemID string) error- Returns an error with message"item not found"if the itemID is"INVALID", otherwise returnsnilProcessOrder(orderID, itemID string) error- CallsValidateItem. If it returns an error, wrap it with context:"processing order [orderID]: %w". Otherwise returnnilSubmitOrder(customerName, orderID, itemID string) error- CallsProcessOrder. If it returns an error, wrap it with context:"submitting order for [customerName]: %w". Otherwise returnnil
Each layer should use
fmt.Errorfwith the%wverb to wrap the error from the layer below, building a chain of context.main.go: Read order details from input, callSubmitOrder, and display the result. If an error occurs, print the full error chain. If successful, print a confirmation message.
The following inputs will be provided:
- Line 1: Customer name
- Line 2: Order ID
- Line 3: Item ID
Print the result:
- If an error occurs:
Error: [full error chain] - If successful:
Order [orderID] submitted successfully for [customerName]
For example, given Alice, ORD-123, and INVALID, your output should be:
Error: submitting order for Alice: processing order ORD-123: item not foundNotice how the error message shows the complete chain—you can trace the problem from the top-level submission attempt, through order processing, down to the actual validation failure. Each layer added its own context using %w.
And given Bob, ORD-456, and ITEM-001, your output should be:
Order ORD-456 submitted successfully for BobCheat sheet
Use fmt.Errorf with the %w verb to wrap errors while preserving the original error:
func ReadConfig(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("reading config file: %w", err)
}
return nil
}The %w verb wraps the original error inside the new one, unlike %v which only converts the error to a string and loses the original error's identity.
Error wrapping creates a chain where each layer adds context:
func LoadSettings() error {
err := ReadConfig("settings.json")
if err != nil {
return fmt.Errorf("loading settings: %w", err)
}
return nil
}The resulting error message shows the full chain: "loading settings: reading config file: open settings.json: no such file or directory"
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Read customer name
scanner.Scan()
customerName := scanner.Text()
// Read order ID
scanner.Scan()
orderID := scanner.Text()
// Read item ID
scanner.Scan()
itemID := scanner.Text()
// TODO: Call SubmitOrder with the input values
// TODO: If an error occurs, print: Error: [full error chain]
// TODO: If successful, print: Order [orderID] submitted successfully for [customerName]
_ = customerName
_ = orderID
_ = itemID
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool