Go > Error Handling > Built-in Error Interface > Unwrapping errors (errors.Unwrap)
Unwrapping Errors in Go
This example demonstrates how to unwrap errors in Go using errors.Unwrap
and how to check for specific error types in a chain of wrapped errors. Understanding error wrapping and unwrapping is crucial for robust error handling and debugging in Go applications.
Understanding Error Wrapping and Unwrapping
Error wrapping allows you to add context to an existing error without losing the original error information. When an error occurs, you can wrap it with additional details, such as the function where the error originated or specific context about the failure. Error unwrapping allows you to retrieve the original error from the wrapped error chain.
Basic Error Wrapping
This code defines a custom error ErrCustom
and two functions, operationA
and operationB
. operationA
returns a wrapped version of ErrCustom
. operationB
calls operationA
and wraps the returned error if it's not nil. The main function then prints the error returned by operationB
. The %w
verb in fmt.Errorf
is used for wrapping errors.
package main
import (
"errors"
"fmt"
)
var ErrCustom = errors.New("custom error")
func operationA() error {
return fmt.Errorf("operationA failed: %w", ErrCustom)
}
func operationB() error {
err := operationA()
if err != nil {
return fmt.Errorf("operationB failed: %w", err)
}
return nil
}
func main() {
err := operationB()
if err != nil {
fmt.Println(err)
}
}
Using errors.Unwrap
This code demonstrates how to use errors.Unwrap
to retrieve the original error from a wrapped error. The errors.Unwrap
function takes an error as input and returns the underlying error, or nil if the error is not wrapped. In this case, we unwrap the error returned by operationB
to retrieve the error returned by operationA
.
package main
import (
"errors"
"fmt"
)
var ErrCustom = errors.New("custom error")
func operationA() error {
return fmt.Errorf("operationA failed: %w", ErrCustom)
}
func operationB() error {
err := operationA()
if err != nil {
return fmt.Errorf("operationB failed: %w", err)
}
return nil
}
func main() {
err := operationB()
if err != nil {
// Unwrap the error to get the original error.
unwrappedErr := errors.Unwrap(err)
fmt.Println("Original error:", unwrappedErr)
}
}
Checking for Specific Errors
This code demonstrates how to use errors.Is
to check if an error, or any error in its chain, matches a specific error. The errors.Is
function traverses the chain of wrapped errors, comparing each error with the target error. If a match is found, it returns true
; otherwise, it returns false
. This allows you to handle specific errors differently based on their type.
package main
import (
"errors"
"fmt"
)
var ErrCustom = errors.New("custom error")
func operationA() error {
return fmt.Errorf("operationA failed: %w", ErrCustom)
}
func operationB() error {
err := operationA()
if err != nil {
return fmt.Errorf("operationB failed: %w", err)
}
return nil
}
func main() {
err := operationB()
if err != nil {
if errors.Is(err, ErrCustom) {
fmt.Println("Custom error occurred!")
}
fmt.Println(err)
}
}
Real-Life Use Case Section
Consider a scenario where you're interacting with a database. You might have layers of functions: one for connecting, another for querying, and another for handling the results. If a database connection fails, the error might be wrapped multiple times as it propagates up the call stack. Unwrapping allows you to check for specific database connection errors (e.g., connection refused) at the top level and respond accordingly (e.g., retry the connection). Without unwrapping, you would only see the top-level error message, which might not be specific enough to guide your recovery actions.
Best Practices
fmt.Errorf
with the %w
verb.errors.Is
to check for specific error types in the chain.errors.As
to check for a custom error type and access its fields.
Interview Tip
When discussing error handling in Go during an interview, be prepared to explain the concepts of error wrapping and unwrapping. Demonstrate your understanding of how errors.Is
and errors.Unwrap
work and how they contribute to writing robust and maintainable code. You can mention how it enables layered error handling and contextual debugging.
When to use them
Use error wrapping when you want to add context to an error without losing the original error information. This is particularly useful when propagating errors up the call stack. Use error unwrapping when you need to inspect the original error or check for specific error types in a chain of wrapped errors.
Memory footprint
Wrapping errors generally adds a small overhead because each wrapped error allocates additional memory to store the error message and a pointer to the wrapped error. However, this overhead is usually negligible compared to the overall memory footprint of the application. The key is to not wrap errors excessively, as excessive wrapping can lead to a deep chain of errors and increase memory usage.
Alternatives
Instead of using errors.Unwrap
directly, you could use errors.As
to check if any error in the chain matches a specific custom error type and access its fields. This is often more convenient than unwrapping errors manually.
Pros
errors.Is
.
Cons
FAQ
-
What is error wrapping in Go?
Error wrapping is the process of adding context to an existing error without losing the original error information. This is done usingfmt.Errorf
with the%w
verb. -
What is error unwrapping in Go?
Error unwrapping is the process of retrieving the original error from a wrapped error. This is done using theerrors.Unwrap
function. -
How do I check for specific errors in a chain of wrapped errors?
You can use theerrors.Is
function to check if an error, or any error in its chain, matches a specific error.