Cookies management by TermsFeed Cookie Consent
Russia has invaded Ukraine and already killed tens of thousands of civilians, with many more raped or tortured. It's a genocide. We need your help. Let's fight back against the Russian regime.
Help Ukraine! Fight the Russian regime!

🌯 Wrap and Unwrap errors in Go

introduction errors

Please consider supporting us by disabling your ad blocker

Wrapping errors in Go means adding extra context information to the returned error like the name of the function where the error occurred, the cause, the type, etc. This technique is most commonly used to create clear error messages, which are especially useful for debugging when you want quickly and precisely locate the source of problems.

To wrap an error in Go, you need to create a new error using the
fmt.Errorf(format string, a ...interface{}) error function with the verb %w in the format:

var ErrorCritical = errors.New("critical error")
...
wrapped := fmt.Errorf("[functionName] internal error: %w", ErrorCritical)

The resulting error is a chain, in which the wrapped error can be ‘unwrapped’ using the errors.Unwrap() function:

fmt.Println(errors.Unwrap(wrapped) == ErrorCritical) // true

It is also possible to check if a given error exists anywhere in the chain thanks to the errors.Is() and errors.As() functions. See the examples below for details on how to wrap, unwrap, and test for error types.

Check out more examples of error handling in Go using the errors.Is() and errors.As() functions in our other tutorial.

Examples

In the first example, we create the getError() function that returns a non-wrap, single-wrap, or double-wrap error depending on the parameter set. The error we wrap is a simple built-in error instance.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
    "errors"
    "fmt"
)

var (
    ErrorInternal = errors.New("internal error")
)

func getError(level int) error {
    level1Err := fmt.Errorf("[getData] level 1 error: %w", ErrorInternal)
    if level == 1 {
        return level1Err
    }
    if level == 2 {
        return fmt.Errorf("[getData] level 2 error: %w", level1Err)
    }

    return ErrorInternal
}

func main() {
    err := getError(1)
    if errors.Is(err, ErrorInternal) {
        fmt.Printf("is error internal: %v\n", err)
    }
    fmt.Printf("unwrapped error: %v\n", errors.Unwrap(err))

    fmt.Printf("---\n")

    err = getError(2)
    if errors.Is(err, ErrorInternal) {
        fmt.Printf("is error internal: %v\n", err)
    }
    unwrapped := errors.Unwrap(err)
    fmt.Printf("unwrapped error: %v\n", unwrapped)
    fmt.Printf("unwrapped unwrapped error: %v\n", errors.Unwrap(unwrapped))
}

Output:

is error internal: [getData] level 1 error: internal error
unwrapped error: internal error
---
is error internal: [getData] level 2 error: [getData] level 1 error: internal error
unwrapped error: [getData] level 1 error: internal error
unwrapped unwrapped error: internal error

Let’s go through the main() function and the output. In lines 25-29, we get a single-wrap error and test if it is an ErrorInternal error. As you can see, the errors.Is() function returns true because it checks if any error in the chain matches the target. It does not matter that the error is wrapped. A simple comparison if err == ErrorInternal would give false in this case, so it is generally a better idea to use the errors.Is() function to compare errors equality. Then, we unwrap the error using the errors.Unwrap() and print it to the standard output. Unwrapping the error gives the ErrorInternal that we wrapped before.

In lines 33-39, we get a double-wrap error. The errors.Is() returns that the ErrorInternal is in the chain, even though it is double-wrapped. As you might expect, a double unwrapping is needed to get to the ErrorInternal.


Similarly, you can wrap, unwrap and test for errors of a specific type. Look at the second example below. The result is analogous to the first example with a simple error instance. The only difference is the use of the errors.As() function instead of errors.Is(), which checks if the error in the chain is of the specific type.

More examples of error handling using the errors.Is() and errors.As() can be found here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
    "errors"
    "fmt"
)

type ErrorInternal struct {
    function string
}

func (e *ErrorInternal) Error() string {
    return fmt.Sprintf("[%s] error internal", e.function)
}

func getError(level int) error {
    level1Err := fmt.Errorf("level 1 error: %w", &ErrorInternal{function: "getData"})
    if level == 1 {
        return level1Err
    }
    if level == 2 {
        return fmt.Errorf("level 2 error: %w", level1Err)
    }

    return &ErrorInternal{function: "getData"}
}

func main() {
    err := getError(1)
    var internalErr *ErrorInternal
    if errors.As(err, &internalErr) {
        fmt.Printf("is error internal: %v\n", err)
    }
    fmt.Printf("unwrapped error: %v\n", errors.Unwrap(err))

    fmt.Printf("---\n")

    err = getError(2)
    if errors.As(err, &internalErr) {
        fmt.Printf("is error internal: %v\n", err)
    }
    unwrapped := errors.Unwrap(err)
    fmt.Printf("unwrapped error: %v\n", unwrapped)
    fmt.Printf("unwrapped unwrapped error: %v\n", errors.Unwrap(unwrapped))
}

Output:

is error internal: level 1 error: [getData] error internal
unwrapped error: [getData] error internal
---
is error internal: level 2 error: level 1 error: [getData] error internal
unwrapped error: level 1 error: [getData] error internal
unwrapped unwrapped error: [getData] error internal

🕵️ Solve 'cannot take address of XXX' error in Go

Learn how to take the address of a literal, map value, or function return value
introduction pointer errors

📂 Check if a file exists in Go

Learn how to check if a file exists in Go after or before opening it
introduction file errors

🐛 Handle errors in Go with errors.Is() and errors.As()

Learn how to check error type using errors.Is() and errors.As() functions
introduction errors