Go > Error Handling > Built-in Error Interface > Wrapping errors
Wrapping Errors in Go
This example demonstrates how to wrap errors in Go, providing context and preserving the original error's information. Error wrapping is crucial for creating robust and maintainable applications.
Understanding Error Wrapping
Error wrapping involves adding context to an existing error. This helps in tracing the error back to its origin and provides more detailed information for debugging. Go 1.13 introduced standard library support for error wrapping using fmt.Errorf
with the %w
verb.
Basic Error Wrapping Example
This code demonstrates a simple error wrapping scenario. The openFile
function attempts to open a file. If an error occurs (e.g., the file doesn't exist), it wraps the original os.Open
error with additional context using fmt.Errorf
and the %w
verb. The errors.Is
function is then used in main
to check if the wrapped error is of type os.ErrNotExist
.
package main
import (
"errors"
"fmt"
"os"
)
func openFile(filename string) error {
_, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", filename, err)
}
return nil
}
func main() {
err := openFile("nonexistent_file.txt")
if err != nil {
fmt.Println(err)
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist.")
}
}
}
Concepts Behind the Snippet
fmt.Errorf
with %w
: The standard way to wrap errors in Go 1.13 and later. The %w
verb embeds the original error within the new error.errors.Is
: A function to check if an error (or any error in its chain of wrapped errors) matches a specific error type.
Real-Life Use Case Section
Imagine a web application. When a database query fails, you might wrap the database error with information about the specific query that failed, the user ID, and other relevant details. This makes debugging much easier.
Best Practices
Interview Tip
Be prepared to discuss the benefits of error wrapping and how it improves error handling in Go. Understand the difference between error wrapping and simply returning a new error.
When to Use Them
Use error wrapping when you want to add context to an error without losing the original error's information. This is particularly useful when propagating errors across different layers of your application.
Alternatives
Before Go 1.13, libraries like github.com/pkg/errors
were commonly used for error wrapping. While still viable, the standard library's fmt.Errorf
with %w
is generally preferred for new projects.
Pros
Cons
More Complex Error Wrapping with Custom Errors
This example extends the concept by using a custom error type CustomError
. The doSomething
function returns a CustomError
. The handleSomething
function wraps this custom error. The errors.As
function is then used in main
to extract the CustomError
from the wrapped error chain, allowing you to access its specific fields like the Code
.
package main
import (
"errors"
"fmt"
)
type CustomError struct {
Message string
Code int
}
func (e *CustomError) Error() string {
return fmt.Sprintf("CustomError: Code=%d, Message=%s", e.Code, e.Message)
}
func doSomething() error {
return &CustomError{Message: "Something went wrong", Code: 500}
}
func handleSomething() error {
err := doSomething()
if err != nil {
return fmt.Errorf("failed to handle something: %w", err)
}
return nil
}
func main() {
err := handleSomething()
if err != nil {
fmt.Println(err)
var customErr *CustomError
if errors.As(err, &customErr) {
fmt.Printf("Custom error code: %d\n", customErr.Code)
}
}
}
FAQ
-
What is the difference between
errors.Is
anderrors.As
?
errors.Is
checks if an error or any error in its chain of wrapped errors matches a specific error value (using==
).errors.As
checks if an error or any error in its chain can be converted to a specific type, filling in a target variable with the converted error if it can. Useerrors.Is
for error values (likeos.ErrNotExist
) anderrors.As
for error types (like*CustomError
). -
Why use error wrapping instead of just returning a new error?
Error wrapping preserves the original error's information, allowing you to inspect the root cause of the error. Simply returning a new error loses this context, making debugging more difficult.