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

  • Always wrap errors when adding context to them. Use fmt.Errorf with the %w verb.
  • Use errors.Is to check for specific error types in the chain.
  • Use errors.As to check for a custom error type and access its fields.
  • Avoid unwrapping errors unnecessarily. Only unwrap when you need to inspect the underlying error.
  • Provide informative error messages when wrapping errors to aid in debugging.

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

  • Provides context to errors, making debugging easier.
  • Allows for layered error handling, where different layers can handle specific errors.
  • Enables more precise error checking using errors.Is.

Cons

  • Can add a small performance overhead due to the extra allocations involved in wrapping errors.
  • Excessive wrapping can lead to a deep chain of errors, which can be difficult to navigate.

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 using fmt.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 the errors.Unwrap function.
  • How do I check for specific errors in a chain of wrapped errors?

    You can use the errors.Is function to check if an error, or any error in its chain, matches a specific error.