Golang error wrap

How to use the new Go 1.13 error interfaces and functions.

The Go Blog

Introduction

Go’s treatment of errors as values
has served us well over the last decade. Although the standard library’s support
for errors has been minimal—just the errors.New and fmt.Errorf functions,
which produce errors that contain only a message—the built-in error interface
allows Go programmers to add whatever information they desire. All it requires
is a type that implements an Error method:

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }

Error types like this one are ubiquitous, and the information they store varies
widely, from timestamps to filenames to server addresses. Often, that
information includes another, lower-level error to provide additional context.

The pattern of one error containing another is so pervasive in Go code that,
after extensive discussion, Go 1.13 added
explicit support for it. This post describes the additions to the standard
library that provide that support: three new functions in the errors package,
and a new formatting verb for fmt.Errorf.

Before describing the changes in detail, let’s review how errors are examined
and constructed in previous versions of the language.

Errors before Go 1.13

Examining errors

Go errors are values. Programs make decisions based on those values in a few
ways. The most common is to compare an error to nil to see if an operation
failed.

if err != nil {
    // something went wrong
}

Sometimes we compare an error to a known sentinel value, to see if a specific error has occurred.

var ErrNotFound = errors.New("not found")

if err == ErrNotFound {
    // something wasn't found
}

An error value may be of any type which satisfies the language-defined error
interface. A program can use a type assertion or type switch to view an error
value as a more specific type.

type NotFoundError struct {
    Name string
}

func (e *NotFoundError) Error() string { return e.Name + ": not found" }

if e, ok := err.(*NotFoundError); ok {
    // e.Name wasn't found
}

Adding information

Frequently a function passes an error up the call stack while adding information
to it, like a brief description of what was happening when the error occurred. A
simple way to do this is to construct a new error that includes the text of the
previous one:

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

Creating a new error with fmt.Errorf discards everything from the original
error except the text. As we saw above with QueryError, we may sometimes want
to define a new error type that contains the underlying error, preserving it for
inspection by code. Here is QueryError again:

type QueryError struct {
    Query string
    Err   error
}

Programs can look inside a *QueryError value to make decisions based on the
underlying error. You’ll sometimes see this referred to as “unwrapping” the
error.

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

The os.PathError type in the standard library is another example of one error which contains another.

Errors in Go 1.13

The Unwrap method

Go 1.13 introduces new features to the errors and fmt standard library
packages to simplify working with errors that contain other errors. The most
significant of these is a convention rather than a change: an error which
contains another may implement an Unwrap method returning the underlying
error. If e1.Unwrap() returns e2, then we say that e1 wraps e2, and
that you can unwrap e1 to get e2.

Following this convention, we can give the QueryError type above an Unwrap
method that returns its contained error:

func (e *QueryError) Unwrap() error { return e.Err }

The result of unwrapping an error may itself have an Unwrap method; we call
the sequence of errors produced by repeated unwrapping the error chain.

Examining errors with Is and As

The Go 1.13 errors package includes two new functions for examining errors: Is and As.

The errors.Is function compares an error to a value.

// Similar to:
//   if err == ErrNotFound { … }
if errors.Is(err, ErrNotFound) {
    // something wasn't found
}

The As function tests whether an error is a specific type.

// Similar to:
//   if e, ok := err.(*QueryError); ok { … }
var e *QueryError
// Note: *QueryError is the type of the error.
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}

In the simplest case, the errors.Is function behaves like a comparison to a
sentinel error, and the errors.As function behaves like a type assertion. When
operating on wrapped errors, however, these functions consider all the errors in
a chain. Let’s look again at the example from above of unwrapping a QueryError
to examine the underlying error:

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

Using the errors.Is function, we can write this as:

if errors.Is(err, ErrPermission) {
    // err, or some error that it wraps, is a permission problem
}

The errors package also includes a new Unwrap function which returns the
result of calling an error’s Unwrap method, or nil when the error has no
Unwrap method. It is usually better to use errors.Is or errors.As,
however, since these functions will examine the entire chain in a single call.

Note: although it may feel odd to take a pointer to a pointer, in this case it
is correct. Think of it instead as taking a pointer to a value of the error
type; it so happens in this case that the returned error is a pointer type.

Wrapping errors with %w

As mentioned earlier, it is common to use the fmt.Errorf function to add additional information to an error.

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

In Go 1.13, the fmt.Errorf function supports a new %w verb. When this verb
is present, the error returned by fmt.Errorf will have an Unwrap method
returning the argument of %w, which must be an error. In all other ways, %w
is identical to %v.

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("decompress %v: %w", name, err)
}

Wrapping an error with %w makes it available to errors.Is and errors.As:

err := fmt.Errorf("access denied: %w", ErrPermission)
...
if errors.Is(err, ErrPermission) ...

Whether to Wrap

When adding additional context to an error, either with fmt.Errorf or by
implementing a custom type, you need to decide whether the new error should wrap
the original. There is no single answer to this question; it depends on the
context in which the new error is created. Wrap an error to expose it to
callers. Do not wrap an error when doing so would expose implementation details.

As one example, imagine a Parse function which reads a complex data structure
from an io.Reader. If an error occurs, we wish to report the line and column
number at which it occurred. If the error occurs while reading from the
io.Reader, we will want to wrap that error to allow inspection of the
underlying problem. Since the caller provided the io.Reader to the function,
it makes sense to expose the error produced by it.

In contrast, a function which makes several calls to a database probably should
not return an error which unwraps to the result of one of those calls. If the
database used by the function is an implementation detail, then exposing these
errors is a violation of abstraction. For example, if the LookupUser function
of your package pkg uses Go’s database/sql package, then it may encounter a
sql.ErrNoRows error. If you return that error with
fmt.Errorf("accessing DB: %v", err)
then a caller cannot look inside to find the sql.ErrNoRows. But if
the function instead returns fmt.Errorf("accessing DB: %w", err), then a
caller could reasonably write

err := pkg.LookupUser(...)
if errors.Is(err, sql.ErrNoRows) …

At that point, the function must always return sql.ErrNoRows if you don’t want
to break your clients, even if you switch to a different database package. In
other words, wrapping an error makes that error part of your API. If you don’t
want to commit to supporting that error as part of your API in the future, you
shouldn’t wrap the error.

It’s important to remember that whether you wrap or not, the error text will be
the same. A person trying to understand the error will have the same information
either way; the choice to wrap is about whether to give programs additional
information so they can make more informed decisions, or to withhold that
information to preserve an abstraction layer.

Customizing error tests with Is and As methods

The errors.Is function examines each error in a chain for a match with a
target value. By default, an error matches the target if the two are
equal. In addition, an
error in the chain may declare that it matches a target by implementing an Is
method.

As an example, consider this error inspired by the
Upspin error package
which compares an error against a template, considering only fields which are
non-zero in the template:

type Error struct {
    Path string
    User string
}

func (e *Error) Is(target error) bool {
    t, ok := target.(*Error)
    if !ok {
        return false
    }
    return (e.Path == t.Path || t.Path == "") &&
           (e.User == t.User || t.User == "")
}

if errors.Is(err, &Error{User: "someuser"}) {
    // err's User field is "someuser".
}

The errors.As function similarly consults an As method when present.

Errors and package APIs

A package which returns errors (and most do) should describe what properties of
those errors programmers may rely on. A well-designed package will also avoid
returning errors with properties that should not be relied upon.

The simplest specification is to say that operations either succeed or fail,
returning a nil or non-nil error value respectively. In many cases, no further
information is needed.

If we wish a function to return an identifiable error condition, such as “item
not found,” we might return an error wrapping a sentinel.

var ErrNotFound = errors.New("not found")

// FetchItem returns the named item.
//
// If no item with the name exists, FetchItem returns an error
// wrapping ErrNotFound.
func FetchItem(name string) (*Item, error) {
    if itemNotFound(name) {
        return nil, fmt.Errorf("%q: %w", name, ErrNotFound)
    }
    // ...
}

There are other existing patterns for providing errors which can be semantically
examined by the caller, such as directly returning a sentinel value, a specific
type, or a value which can be examined with a predicate function.

In all cases, care should be taken not to expose internal details to the user.
As we touched on in “Whether to Wrap” above, when you return
an error from another package you should convert the error to a form that does
not expose the underlying error, unless you are willing to commit to returning
that specific error in the future.

f, err := os.Open(filename)
if err != nil {
    // The *os.PathError returned by os.Open is an internal detail.
    // To avoid exposing it to the caller, repackage it as a new
    // error with the same text. We use the %v formatting verb, since
    // %w would permit the caller to unwrap the original *os.PathError.
    return fmt.Errorf("%v", err)
}

If a function is defined as returning an error wrapping some sentinel or type,
do not return the underlying error directly.

var ErrPermission = errors.New("permission denied")

// DoSomething returns an error wrapping ErrPermission if the user
// does not have permission to do something.
func DoSomething() error {
    if !userHasPermission() {
        // If we return ErrPermission directly, callers might come
        // to depend on the exact error value, writing code like this:
        //
        //     if err := pkg.DoSomething(); err == pkg.ErrPermission { … }
        //
        // This will cause problems if we want to add additional
        // context to the error in the future. To avoid this, we
        // return an error wrapping the sentinel so that users must
        // always unwrap it:
        //
        //     if err := pkg.DoSomething(); errors.Is(err, pkg.ErrPermission) { ... }
        return fmt.Errorf("%w", ErrPermission)
    }
    // ...
}

Conclusion

Although the changes we’ve discussed amount to just three functions and a
formatting verb, we hope they will go a long way toward improving how errors are
handled in Go programs. We expect that wrapping to provide additional context
will become commonplace, helping programs to make better decisions and helping
programmers to find bugs more quickly.

As Russ Cox said in his GopherCon 2019 keynote,
on the path to Go 2 we experiment, simplify and ship. Now that we’ve
shipped these changes, we look forward to the experiments that will follow.

Package errors implements functions to manipulate errors.

The New function creates errors whose only content is a text message.

An error e wraps another error if e’s type has one of the methods

Unwrap() error
Unwrap() []error

If e.Unwrap() returns a non-nil error w or a slice containing w,
then we say that e wraps w. A nil error returned from e.Unwrap()
indicates that e does not wrap any error. It is invalid for an
Unwrap method to return an []error containing a nil error value.

An easy way to create wrapped errors is to call fmt.Errorf and apply
the %w verb to the error argument:

wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)

Successive unwrapping of an error creates a tree. The Is and As
functions inspect an error’s tree by examining first the error
itself followed by the tree of each of its children in turn
(pre-order, depth-first traversal).

Is examines the tree of its first argument looking for an error that
matches the second. It reports whether it finds a match. It should be
used in preference to simple equality checks:

if errors.Is(err, fs.ErrExist)

is preferable to

if err == fs.ErrExist

because the former will succeed if err wraps fs.ErrExist.

As examines the tree of its first argument looking for an error that can be
assigned to its second argument, which must be a pointer. If it succeeds, it
performs the assignment and returns true. Otherwise, it returns false. The form

var perr *fs.PathError
if errors.As(err, &perr) {
	fmt.Println(perr.Path)
}

is preferable to

if perr, ok := err.(*fs.PathError); ok {
	fmt.Println(perr.Path)
}

because the former will succeed if err wraps an *fs.PathError.

package main

import (
	"fmt"
	"time"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
	When time.Time
	What string
}

func (e MyError) Error() string {
	return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
	return MyError{
		time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
		"the file system has gone away",
	}
}

func main() {
	if err := oops(); err != nil {
		fmt.Println(err)
	}
}
Output:

1989-03-15 22:30:00 +0000 UTC: the file system has gone away
  • func As(err error, target any) bool
  • func Is(err, target error) bool
  • func Join(errs …error) error
  • func New(text string) error
  • func Unwrap(err error) error
  • Package
  • As
  • Is
  • Join
  • New
  • New (Errorf)
  • Unwrap

This section is empty.

This section is empty.

As finds the first error in err’s tree that matches target, and if one is found, sets
target to that error value and returns true. Otherwise, it returns false.

The tree consists of err itself, followed by the errors obtained by repeatedly
calling Unwrap. When err wraps multiple errors, As examines err followed by a
depth-first traversal of its children.

An error matches target if the error’s concrete value is assignable to the value
pointed to by target, or if the error has a method As(interface{}) bool such that
As(target) returns true. In the latter case, the As method is responsible for
setting target.

An error type might provide an As method so it can be treated as if it were a
different error type.

As panics if target is not a non-nil pointer to either a type that implements
error, or to any interface type.

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		var pathError *fs.PathError
		if errors.As(err, &pathError) {
			fmt.Println("Failed at path:", pathError.Path)
		} else {
			fmt.Println(err)
		}
	}

}
Output:

Failed at path: non-existing

Is reports whether any error in err’s tree matches target.

The tree consists of err itself, followed by the errors obtained by repeatedly
calling Unwrap. When err wraps multiple errors, Is examines err followed by a
depth-first traversal of its children.

An error is considered to match a target if it is equal to that target or if
it implements a method Is(error) bool such that Is(target) returns true.

An error type might provide an Is method so it can be treated as equivalent
to an existing error. For example, if MyError defines

func (m MyError) Is(target error) bool { return target == fs.ErrExist }

then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for
an example in the standard library. An Is method should only shallowly
compare err and the target and not call Unwrap on either.

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			fmt.Println("file does not exist")
		} else {
			fmt.Println(err)
		}
	}

}
Output:

file does not exist

Join returns an error that wraps the given errors.
Any nil error values are discarded.
Join returns nil if errs contains no non-nil values.
The error formats as the concatenation of the strings obtained
by calling the Error method of each element of errs, with a newline
between each string.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("err1")
	err2 := errors.New("err2")
	err := errors.Join(err1, err2)
	fmt.Println(err)
	if errors.Is(err, err1) {
		fmt.Println("err is err1")
	}
	if errors.Is(err, err2) {
		fmt.Println("err is err2")
	}
}
Output:

err1
err2
err is err1
err is err2

New returns an error that formats as the given text.
Each call to New returns a distinct error value even if the text is identical.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Print(err)
	}
}
Output:

emit macho dwarf: elf header corrupted

The fmt package’s Errorf function lets us use the package’s formatting
features to create descriptive error messages.

package main

import (
	"fmt"
)

func main() {
	const name, id = "bimmler", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	if err != nil {
		fmt.Print(err)
	}
}
Output:

user "bimmler" (id 17) not found

Unwrap returns the result of calling the Unwrap method on err, if err’s
type contains an Unwrap method returning error.
Otherwise, Unwrap returns nil.

Unwrap returns nil if the Unwrap method returns []error.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("error1")
	err2 := fmt.Errorf("error2: [%w]", err1)
	fmt.Println(err2)
	fmt.Println(errors.Unwrap(err2))
	// Output
	// error2: [error1]
	// error1
}
Output:

This section is empty.

В последнее десятилетие мы успешно пользовались тем, что Go обрабатывает ошибки как значения. Хотя в стандартной библиотеке была минимальная поддержка ошибок: лишь функции errors.New и fmt.Errorf, которые генерируют ошибку, содержащую только сообщение — встроенный интерфейс позволяет Go-программистам добавлять любую информацию. Нужен лишь тип, реализующий метод Error:

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }

Такие типы ошибок встречаются во всех языках и хранят самую разную информацию, от временных меток до названий файлов и адресов серверов. Часто упоминаются и низкоуровневые ошибки, предоставляющие дополнительный контекст.

Паттерн, когда одна ошибка содержит другую, встречается в Go столь часто, что после жаркой дискуссии в Go 1.13 была добавлена его явная поддержка. В этой статье мы рассмотрим дополнения к стандартной библиотеке, обеспечивающие упомянутую поддержку: три новые функции в пакете errors и новая форматирующая команда для fmt.Errorf.

Прежде чем подробно рассматривать изменения, давайте поговорим о том, как ошибки исследовались и конструировались в предыдущих версиях языка.

Ошибки до Go 1.13

Исследование ошибок

Ошибки в Go являются значениями. Программы принимают решения на основе этих значений разными способами. Чаще всего ошибка сравнивается с nil, чтобы понять, не было ли сбоя операции.

if err != nil {
    // something went wrong
}

Иногда мы сравниваем ошибку, чтобы узнать контрольное значение и понять, не возникла ли конкретная ошибка.

var ErrNotFound = errors.New("not found")

if err == ErrNotFound {
    // something wasn't found
}

Значение-ошибка может быть любого типа, который удовлетворяет определённому в языке интерфейсу ошибок. Программа может использовать утверждение типа или переключатель типа для просмотра значения-ошибки более специфического типа.

type NotFoundError struct {
    Name string
}

func (e *NotFoundError) Error() string { return e.Name + ": not found" }

if e, ok := err.(*NotFoundError); ok {
    // e.Name wasn't found
}

Добавление информации

Зачастую функция передаёт ошибку вверх по стеку вызовов, добавляя к ней информацию, например, короткое описание того, что происходило в момент возникновения ошибки. Это сделать просто, достаточно сконструировать новую ошибку, включающую в себя текст из предыдущей ошибки:

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

При создании новой ошибки с помощью fmt.Errorf мы выбрасываем из исходной ошибки всё, за исключением текста. Как мы видели в примере с QueryError, иногда нужно определять новый тип ошибки, который содержит исходную ошибку, чтобы сохранить её для анализа с помощью кода:

type QueryError struct {
    Query string
    Err   error
}

Программы могут заглянуть внутрь значения *QueryError и принять решение на основе исходной ошибки. Иногда это называется «распаковкой» (unwrapping) ошибки.

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

Тип os.PathError из стандартной библиотеки — ещё пример того, как одна ошибка содержит другую.

Ошибки в Go 1.13

Метод Unwrap

В Go 1.13 в пакетах стандартной библиотеки errors и fmt упрощена работа с ошибками, которые содержат другие ошибки. Самым важным является соглашение, а не изменение: ошибка, содержащая другую ошибку, может реализовать метод Unwrap, который возвращает исходную ошибку. Если e1.Unwrap() возвращает e2, то мы говорим, что e1 упаковывает e2 и можно распаковать e1 для получения e2.

Согласно этому соглашению, можно дать описанный выше тип QueryError методу Unwrap, который возвращает содержащуюся в нём ошибку:

func (e *QueryError) Unwrap() error { return e.Err }

Результат распаковки ошибки тоже может содержать метод Unwrap. Последовательность ошибок, полученных с помощью повторяющихся распаковок, мы называем цепочкой ошибок.

Исследование ошибок с помощью Is и As

В Go 1.13 пакет errors содержит две новые функции для исследования ошибок: Is и As.

Функция errors.Is сравнивает ошибку со значением.

// Similar to:
//   if err == ErrNotFound { … }
if errors.Is(err, ErrNotFound) {
    // something wasn't found
}

Функция As проверяет, относится ли ошибка к конкретному типу.

// Similar to:
//   if e, ok := err.(*QueryError); ok { … }
var e *QueryError
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}

В простейшем случае функция errors.Is ведёт себя как сравнение с контрольной ошибкой, а функция errors.As ведёт себя как утверждение типа. Однако работая с упакованными ошибками, эти функции оценивают все ошибки в цепочке. Давайте посмотрим на вышеприведённый пример распаковки QueryError для исследования исходной ошибки:

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

С помощью функции errors.Is можно записать так:

if errors.Is(err, ErrPermission) {
    // err, or some error that it wraps, is a permission problem
}

Пакет errors также содержит новую функцию Unwrap, которая возвращает результат вызова метода Unwrap ошибки, или возвращает nil, если у ошибки нет метода Unwrap. Обычно лучше использовать errors.Is или errors.As, поскольку они позволяют исследовать всю цепочку одним вызовом.

Упаковка ошибок с помощью %w

Как я упоминал, нормальной практикой является использование функции fmt.Errorf для добавления к ошибке дополнительной информации.

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

В Go 1.13 функция fmt.Errorf поддерживает новая команда %w. Если она есть, то ошибка, возвращаемая fmt.Errorf, будет содержать метод Unwrap, возвращающий аргумент %w, который должен быть ошибкой. Во всех остальных случаях %w идентична %v.

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("decompress %v: %w", name, err)
}

Упаковка ошибки с помощью %w делает её доступной для errors.Is и errors.As:

err := fmt.Errorf("access denied: %w", ErrPermission)
...
if errors.Is(err, ErrPermission) ...

Когда стоит упаковывать?

Когда вы добавляете к ошибке дополнительный контекст с помощью fmt.Errorf или реализации пользовательского типа вам нужно решить, будет ли новая ошибка содержать в себе исходную. На это нет однозначного ответа, всё зависит от контекста, в котором создана новая ошибка. Упакуйте, чтобы показать её вызывающим. Не упаковывайте ошибку, если это приведёт к раскрытию подробностей реализации.

Например, представьте функцию Parse, которая считывает из io.Reader сложную структуру данных. Если возникает ошибка, нам захочется узнать номер строки и столбца, где она произошла. Если ошибка возникла при чтении из io.Reader, нам нужно будет упаковать её, чтобы выяснить причину. Поскольку вызывающий был предоставлен функции io.Reader, имеет смысл показать сгенерированную им ошибку.

Другой случай: функция, которая делает несколько вызовов базы данных, вероятно, не должна возвращать ошибку, в которой упакован результат одного из этих вызовов. Если БД, которая использовалась этой функцией, является частью реализации, то раскрытие этих ошибок нарушит абстракцию. К примеру, если функция LookupUser из пакета pkg использует пакет Go database/sql, то она может столкнуться с ошибкой sql.ErrNoRows. Если вернуть ошибку с помощью fmt.Errorf("accessing DB: %v", err), тогда вызывающий не может заглянуть внутрь и найти sql.ErrNoRows. Но если функция вернёт fmt.Errorf("accessing DB: %w", err), тогда вызывающий мог бы написать:

err := pkg.LookupUser(...)
if errors.Is(err, sql.ErrNoRows) …

В таком случае функция должна всегда возвращать sql.ErrNoRows, если вы не хотите сломать клиенты, даже при переключении на пакет с другой базой данных. Иными словами, упаковка делает ошибку частью вашего API. Если не хотите в будущем коммитить поддержку этой ошибки как часть API, не упаковывайте её.

Важно помнить, что вне зависимости от того, упаковываете вы её или нет, ошибка останется неизменной. Человек, который будет в ней разбираться, будет иметь одну и ту же информацию. Принятие решения об упаковке зависит от того, нужно ли дать дополнительную информацию программам, чтобы они могли принимать более информированные решения; или если нужно скрыть эту информацию ради сохранения уровня абстракции.

Настройка тестирования ошибок с помощью методов Is и As

Функция errors.Is проверяет каждую ошибку в цепочке на соответствие целевому значению. По умолчанию ошибка соответствует этому значению, если они эквивалентны. Кроме того, ошибка в цепочке может объявлять о своём соответствии целевому значению с помощью реализации метода Is.

Рассмотрим ошибку, вызванную пакетом Upspin, которая сравнивает ошибку с шаблоном и оценивает только ненулевые поля:

type Error struct {
    Path string
    User string
}

func (e *Error) Is(target error) bool {
    t, ok := target.(*Error)
    if !ok {
        return false
    }
    return (e.Path == t.Path || t.Path == "") &&
           (e.User == t.User || t.User == "")
}

if errors.Is(err, &Error{User: "someuser"}) {
    // err's User field is "someuser".
}

Функция errors.As также консультирует метод As при его наличии.

Ошибки и API пакетов

Пакет, который возвращает ошибки (а это делают большинство пакетов), должен описать свойства этих ошибок, на которые может опираться программист. Хорошо спроектированный пакет также будет избегать возвращения ошибок со свойствами, на которые нельзя опираться.

Самое простое: говорить, была ли операция успешной, возвращая, соответственно, значение nil или не-nil. Во многих случаях другой информации не требуется.

Если вам нужно, чтобы функция возвращала индентифицируемое состояние ошибки, например, «элемент не найден», то можно возвращать ошибку, в которую упаковано сигнальное значение.

var ErrNotFound = errors.New("not found")

// FetchItem returns the named item.
//
// If no item with the name exists, FetchItem returns an error
// wrapping ErrNotFound.
func FetchItem(name string) (*Item, error) {
    if itemNotFound(name) {
        return nil, fmt.Errorf("%q: %w", name, ErrNotFound)
    }
    // ...
}

Есть и другие паттерны предоставления ошибок, которые вызывающий может семантически изучить. Например, напрямую возвращать контрольное значение, конкретный тип, или значение, которое можно проанализировать с помощью предикативной функции.

В любом случае, не раскрывайте пользователю внутренние подробности. Как упоминалось в главе «Когда стоит упаковывать?», если возвращаете ошибку из другого пакета, то преобразуйте её, чтобы не раскрывать исходную ошибку, если только не собираетесь брать на себя обязательство в будущем вернуть эту конкретную ошибку.

f, err := os.Open(filename)
if err != nil {
    // The *os.PathError returned by os.Open is an internal detail.
    // To avoid exposing it to the caller, repackage it as a new
    // error with the same text. We use the %v formatting verb, since
    // %w would permit the caller to unwrap the original *os.PathError.
    return fmt.Errorf("%v", err)
}

Если функция возвращает ошибку с упакованным сигнальным значением или типом, то не возвращайте напрямую исходную ошибку.

var ErrPermission = errors.New("permission denied")

// DoSomething returns an error wrapping ErrPermission if the user
// does not have permission to do something.
func DoSomething() {
    if !userHasPermission() {
        // If we return ErrPermission directly, callers might come
        // to depend on the exact error value, writing code like this:
        //
        //     if err := pkg.DoSomething(); err == pkg.ErrPermission { … }
        //
        // This will cause problems if we want to add additional
        // context to the error in the future. To avoid this, we
        // return an error wrapping the sentinel so that users must
        // always unwrap it:
        //
        //     if err := pkg.DoSomething(); errors.Is(err, pkg.ErrPermission) { ... }
        return fmt.Errorf("%w", ErrPermission)
    }
    // ...
}

Заключение

Хотя мы обсудили всего лишь три функции и форматирующую команду, надеемся, что они помогут сильно улучшить обработку ошибок в программах на Go. Мы надеемся, что упаковка ради предоставления дополнительного контекста станет нормальной практикой, помогающей программистам принимать более взвешенные решения и быстрее находить баги.

Как сказал Расс Кокс (Russ Cox) в своём выступлении на GopherCon 2019, на пути к Go 2 мы экспериментируем, упрощаем и отгружаем. И теперь, отгрузив эти изменения, мы принимаемся за новые эксперименты.

import "errors"
Overview
Index
Examples

Overview ▸

Overview ▾

Package errors implements functions to manipulate errors.

The New function creates errors whose only content is a text message.

An error e wraps another error if e’s type has one of the methods

Unwrap() error
Unwrap() []error

If e.Unwrap() returns a non-nil error w or a slice containing w,
then we say that e wraps w. A nil error returned from e.Unwrap()
indicates that e does not wrap any error. It is invalid for an
Unwrap method to return an []error containing a nil error value.

An easy way to create wrapped errors is to call fmt.Errorf and apply
the %w verb to the error argument:

wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)

Successive unwrapping of an error creates a tree. The Is and As
functions inspect an error’s tree by examining first the error
itself followed by the tree of each of its children in turn
(pre-order, depth-first traversal).

Is examines the tree of its first argument looking for an error that
matches the second. It reports whether it finds a match. It should be
used in preference to simple equality checks:

if errors.Is(err, fs.ErrExist)

is preferable to

if err == fs.ErrExist

because the former will succeed if err wraps fs.ErrExist.

As examines the tree of its first argument looking for an error that can be
assigned to its second argument, which must be a pointer. If it succeeds, it
performs the assignment and returns true. Otherwise, it returns false. The form

var perr *fs.PathError
if errors.As(err, &perr) {
	fmt.Println(perr.Path)
}

is preferable to

if perr, ok := err.(*fs.PathError); ok {
	fmt.Println(perr.Path)
}

because the former will succeed if err wraps an *fs.PathError.

Example

Example

1989-03-15 22:30:00 +0000 UTC: the file system has gone away

Index ▸

func As

1.13

func As(err error, target any) bool

As finds the first error in err’s tree that matches target, and if one is found, sets
target to that error value and returns true. Otherwise, it returns false.

The tree consists of err itself, followed by the errors obtained by repeatedly
calling Unwrap. When err wraps multiple errors, As examines err followed by a
depth-first traversal of its children.

An error matches target if the error’s concrete value is assignable to the value
pointed to by target, or if the error has a method As(interface{}) bool such that
As(target) returns true. In the latter case, the As method is responsible for
setting target.

An error type might provide an As method so it can be treated as if it were a
different error type.

As panics if target is not a non-nil pointer to either a type that implements
error, or to any interface type.

Example

Example

Failed at path: non-existing

func Is

1.13

func Is(err, target error) bool

Is reports whether any error in err’s tree matches target.

The tree consists of err itself, followed by the errors obtained by repeatedly
calling Unwrap. When err wraps multiple errors, Is examines err followed by a
depth-first traversal of its children.

An error is considered to match a target if it is equal to that target or if
it implements a method Is(error) bool such that Is(target) returns true.

An error type might provide an Is method so it can be treated as equivalent
to an existing error. For example, if MyError defines

func (m MyError) Is(target error) bool { return target == fs.ErrExist }

then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for
an example in the standard library. An Is method should only shallowly
compare err and the target and not call Unwrap on either.

func Join

1.20

func Join(errs ...error) error

Join returns an error that wraps the given errors.
Any nil error values are discarded.
Join returns nil if errs contains no non-nil values.
The error formats as the concatenation of the strings obtained
by calling the Error method of each element of errs, with a newline
between each string.

Example

Example

err1
err2
err is err1
err is err2

func New

func New(text string) error

New returns an error that formats as the given text.
Each call to New returns a distinct error value even if the text is identical.

Example

Example

emit macho dwarf: elf header corrupted

Example (Errorf)

Example (Errorf)

The fmt package’s Errorf function lets us use the package’s formatting
features to create descriptive error messages.

user "bimmler" (id 17) not found

func Unwrap

1.13

func Unwrap(err error) error

Unwrap returns the result of calling the Unwrap method on err, if err’s
type contains an Unwrap method returning error.
Otherwise, Unwrap returns nil.

Unwrap returns nil if the Unwrap method returns []error.

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: %vn", err)
    }
    fmt.Printf("unwrapped error: %vn", errors.Unwrap(err))

    fmt.Printf("---n")

    err = getError(2)
    if errors.Is(err, ErrorInternal) {
        fmt.Printf("is error internal: %vn", err)
    }
    unwrapped := errors.Unwrap(err)
    fmt.Printf("unwrapped error: %vn", unwrapped)
    fmt.Printf("unwrapped unwrapped error: %vn", 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: %vn", err)
    }
    fmt.Printf("unwrapped error: %vn", errors.Unwrap(err))

    fmt.Printf("---n")

    err = getError(2)
    if errors.As(err, &internalErr) {
        fmt.Printf("is error internal: %vn", err)
    }
    unwrapped := errors.Unwrap(err)
    fmt.Printf("unwrapped error: %vn", unwrapped)
    fmt.Printf("unwrapped unwrapped error: %vn", 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

Errors are a core part of almost every programming language, and how we handle them is a critical part of software development. One of the things that I really enjoy about programming in Go is the implementation of errors and how they are treated: Effective without having unnecessary complexity. This blog post will dive into what errors are and how they can be wrapped (and unwrapped).

What are errors?

Let’s start from the beginning. It’s common to see an error getting returned and handled from a function:

1
2
3
func myFunction() error {
    // ...
}

But what exactly is an error? It is one of the simplest interfaces defined in Go (source code reference):

1
2
3
type error interface {
	Error() string
}

It has a single function Error that takes no parameters and returns a string. That’s it! That’s all there is to implementing the error interface. We’ll see later on how we can implement error to create our own custom error types.

Creating errors

Most of the time you’ll rely on creating errors through one of two ways:

1
fmt.Errorf("error doing something")

Or:

1
errors.New("error doing something")

The former is used when you want to use formatting with the typical fmt verbs. If you aren’t wrapping an error (more on this below) then fmt.Errorf effectively makes a call to errors.New (source code reference). So if you’re not wrapping an error or using any additional formatting then it’s a personal preference.

What do these non-wrapped errors look like? Breaking into the debugger we can analyze them:

1
2
3
(dlv) p err1
error(*errors.errorString) *{
        s: "error doing something",}

The concrete type is *errors.errorString. Let’s take a look at this Go struct in the errors package (source code reference):

1
2
3
4
5
6
7
type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

errorString is a simple implementation having a single string field and because this implements the error interface it defines the Error function, which just returns the struct’s string.

Creating custom error types

What if we want to create custom errors with certain pieces of information? Now that we understand what an error actually is (by implementing the error interface) we can define our own:

1
2
3
4
5
6
7
8
type myCustomError struct {
    errorMessage    string
    someRandomValue int
}

func (e *myCustomError) Error() string {
    return fmt.Sprintf("Message: %s - Random value: %d", e.errorMessage, e.someRandomValue)
}

And we can use them just like any other error in Go:

1
2
3
4
5
6
7
8
9
10
11
func someFunction() error {
    return &myCustomError{
        errorMessage:    "hello world",
        someRandomValue: 13,
    }
}

func main() {
    err := someFunction()
    fmt.Printf("%v", err)
}

The output of running this code is expected:

1
Message: hello world - Random value: 13

Error wrapping

In Go it is common to return errors and then keep bubbling that up until it is handled properly (exiting, logging, etc.). Consider this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
func doAnotherThing() error {
    return errors.New("error doing another thing")
}

func doSomething() error {
    err := doAnotherThing()
    return fmt.Errorf("error doing something: %v", err)
}

func main() {
    err := doSomething()
    fmt.Println(err)
}

main makes a call to doSomething, which calls doAnotherThing and takes its error.

Note: It’s common to have error handling with if err != nil ... but I wanted to keep this example as small as possible.

Typically you want to preserve the context of your inner errors (in this case “error doing another thing”) so you might try to do a superficial wrap with fmt.Errorf and the %v verb. In fact, the output seems reasonable:

1
error doing something: error doing another thing

But outside of wrapping the error messages, we’ve lost the inner errors themselves effectively. If we were to analyze err in main, we’d see this:

1
2
3
(dlv) p err
error(*errors.errorString) *{
        s: "error doing something: error doing another thing",}

Most of the time that is typically fine. A superficially wrapper error is ok for logging and troubleshooting. But what happens if you need to programmatically test for a particular error or treat an error as a custom one? With the above approach, that is extremely complicated and error-prone.

The solution to that challenge is by wrapping your errors. To wrap your errors you would use fmt.Errorf with the %w verb. Let’s modify the single line of code in the above example:

1
return fmt.Errorf("error doing something: %w", err)

Now let’s inspect the returned error in main:

1
2
3
4
5
(dlv) p err
error(*fmt.wrapError) *{
        msg: "error doing something: error doing another thing",
        err: error(*errors.errorString) *{
                s: "error doing another thing",},}

We’re no longer getting the type *errors.errorString. Now we have the type *fmt.wrapError. Let’s take a look at how Go defines wrapError (source code reference):

1
2
3
4
5
6
7
8
9
10
11
12
type wrapError struct {
	msg string
	err error
}

func (e *wrapError) Error() string {
	return e.msg
}

func (e *wrapError) Unwrap() error {
	return e.err
}

This adds a couple of new things:

  1. err field with the type error (this will be the inner/wrapped error)
  2. Unwrap method that gives us access to the inner/wrapped error

This extra wiring gives us a lot of powerful capabilities when dealing with wrapped errors.

Error equality

One of the scenarios that error wrapping unlocks is a really elegant way to test if an error or any inner/wrapped errors are a particular error. We can do that with the errors.Is function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var errAnotherThing = errors.New("error doing another thing")

func doAnotherThing() error {
    return errAnotherThing
}

func doSomething() error {
    err := doAnotherThing()
    return fmt.Errorf("error doing something: %w", err)
}

func main() {
    err := doSomething()

    if errors.Is(err, errAnotherThing) {
        fmt.Println("Found error!")
    }

    fmt.Println(err)
}

I changed the code of doAnotherThing to return a particular error (errAnotherThing). Even though this error gets wrapped in doSomething, we’re still able to concisely test if the returned error is or wraps errAnotherThing with errors.Is.

errors.Is essentially just loops through the different layers of the error and unwraps, testing to see if it is equal to the target error (source code reference).

Specific error handling

Another scenario is if you have a particular type of error that you want to handle, even if it is wrapped. Using a variation of an earlier example:

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
type myCustomError struct {
    errorMessage    string
    someRandomValue int
}

func (e *myCustomError) Error() string {
    return fmt.Sprintf("Message: %s - Random value: %d", e.errorMessage, e.someRandomValue)
}

func doAnotherThing() error {
    return &myCustomError{
        errorMessage:    "hello world",
        someRandomValue: 13,
    }
}

func doSomething() error {
    err := doAnotherThing()
    return fmt.Errorf("error doing something: %w", err)
}

func main() {
    err := doSomething()

    var customError *myCustomError
    if errors.As(err, &customError) {
        fmt.Printf("Custom error random value: %dn", customError.someRandomValue)
    }

    fmt.Println(err)
}

This allows us to handle err in main if it (or any wrapped errors) have a concrete type of *myCustomError. The output of running this code:

1
2
Custom error random value: 13
error doing something: Message: hello world - Random value: 13

Summary

Understanding how errors and error wrapping in Go can go a really long way in implementing them in the best possible way. Using them “the Go way” can lead to code that is easier to maintain and troubleshoot. Enjoy!

In go, error can wrap another error as well.  What does the wrapping of error mean? It means to create a hierarchy of errors in which a  particular instance of error wraps another error and that particular instance itself can be wrapped inside another error.  Below is the syntax for wrapping an error

e := fmt.Errorf("... %w ...", ..., err, ...)

%w directive Is used for wrapping the error.  The fmt.Errorf should be called with only one %w directive. Let’s see an example.

package main

import (
	"fmt"
)

type errorOne struct{}

func (e errorOne) Error() string {
	return "Error One happended"
}

func main() {

	e1 := errorOne{}

	e2 := fmt.Errorf("E2: %w", e1)

	e3 := fmt.Errorf("E3: %w", e2)

	fmt.Println(e2)

	fmt.Println(e3)

}

Output

E2: Error One happended
E3: E2: Error One happended

In the above program, we created a struct errorOne that has an Error method hence it implements the error interface. Then we created an instance of the errorOne struct named e1. Then we wrapped that instance e1 into another error e2 like this

e2 := fmt.Errorf("E2: %w", e1)

Then we wrapped e2 into e3 like below. 

e3 := fmt.Errorf("E3: %w", e2)

So so we created a hierarchy of errors in which e3 wraps e2 and e2 wraps e1.  Thus e3 also wraps e1 transitively. When we print e2  it also prints the error from e1 and gives the output.

E2: Error One happended

When we print e3 it prints the error from e2 as well as e1 and gives the output.

E3: E2: Error One happended

Now the question which comes to the mind that whats the use case of wrapping the errors. To understand it let’s see an example

package main

import (
	"fmt"
)

type notPositive struct {
	num int
}

func (e notPositive) Error() string {
	return fmt.Sprintf("checkPositive: Given number %d is not a positive number", e.num)
}

type notEven struct {
	num int
}

func (e notEven) Error() string {
	return fmt.Sprintf("checkEven: Given number %d is not an even number", e.num)
}

func checkPositive(num int) error {
	if num < 0 {
		return notPositive{num: num}
	}
	return nil
}

func checkEven(num int) error {
	if num%2 == 1 {
		return notEven{num: num}
	}
	return nil
}

func checkPostiveAndEven(num int) error {
	if num > 100 {
		return fmt.Errorf("checkPostiveAndEven: Number %d is greater than 100", num)
	}

	err := checkPositive(num)
	if err != nil {
		return err
	}

	err = checkEven(num)
	if err != nil {
		return err
	}

	return nil
}

func main() {
	num := 3
	err := checkPostiveAndEven(num)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Givennnumber is positive and even")
	}

}

Output

checkEven: Given number 3 is not an even number

In the above program, we have a function checkPostiveAndEven that checks whether a number is even and positive. In turn, it calls the checkEven function to check if the number is even. And then it calls checkPositive function to check if the number is positive. If a number is not even and positive it an error is raised.

In the above program, it is impossible to tell stack trace of the error. We know that this error came from checkEven function for the above output. But which function called the checkEven function is not clear from the error. This is where wrapping the error comes in the picture.  This becomes more useful when the project is big and there are a lot of functions calling each other.  Let’s rewrite the program by wrapping the error.

package main

import (
	"fmt"
)

type notPositive struct {
	num int
}

func (e notPositive) Error() string {
	return fmt.Sprintf("checkPositive: Given number %d is not a positive number", e.num)
}

type notEven struct {
	num int
}

func (e notEven) Error() string {
	return fmt.Sprintf("checkEven: Given number %d is not an even number", e.num)
}

func checkPositive(num int) error {
	if num < 0 {
		return notPositive{num: num}
	}
	return nil
}

func checkEven(num int) error {
	if num%2 == 1 {
		return notEven{num: num}
	}
	return nil
}

func checkPostiveAndEven(num int) error {
	if num > 100 {
		return fmt.Errorf("checkPostiveAndEven: Number %d is greater than 100", num)
	}

	err := checkPositive(num)
	if err != nil {
		return fmt.Errorf("checkPostiveAndEven: %w", err)
	}

	err = checkEven(num)
	if err != nil {
		return fmt.Errorf("checkPostiveAndEven: %w", err)
	}

	return nil
}

func main() {
	num := 3
	err := checkPostiveAndEven(num)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Given number is positive and even")
	}

}

Output

checkPostiveAndEven: checkEven: Given number 3 is not an even number

 The above program is same as the previous program just that in the checkPostiveAndEven function , we wrap the errors like below.

fmt.Errorf("checkPostiveAndEven: %w", err)

So the output is more clear and the error is more informative. The output clearly mentions the sequence of calling as well

checkPostiveAndEven: checkEven: Given number 3 is not an even number

Unwrap an error

In the above section, we studied about wrapping the error. It is also possible to unwrap the error. Unwrap function of errors package can be used to unwrap an error. Below is the syntax of the function.

func Unwrap(err error) error

If the err wraps another error, then the wrapped error will be returned otherwise Unwrap function will return nil.

Let’s see a program to illustrate the same

import (
    "errors"
    "fmt"
)
type errorOne struct{}
func (e errorOne) Error() string {
    return "Error One happended"
}
func main() {
    e1 := errorOne{}
    e2 := fmt.Errorf("E2: %w", e1)
    e3 := fmt.Errorf("E3: %w", e2)
    fmt.Println(errors.Unwrap(e3))
    fmt.Println(errors.Unwrap(e2))
    fmt.Println(errors.Unwrap(e1))
}

Output

E2: Error One happended
Error One happended

In the above program, we created a struct errorOne that has an Error method hence it implements the error interface. Then we created an instance of the errorOne struct named e1. Then we wrapped that instance e1 into another error e2 like this

e2 := fmt.Errorf("E2: %w", e1)

Then we wrapped e2 into e3 like below. 

e3 := fmt.Errorf("E3: %w", e2)

Hence

fmt.Println(errors.Unwrap(e3))

will return wrapped error e2, as e3 wraps e2 and output will be

E2: Error One happended

Also,

fmt.Println(errors.Unwrap(e2))

will return wrapped error e1 as e2 further wraps e1 and output will be

Error One happened

While

fmt.Println(errors.Unwrap(e1))

will output nil as e1 does not wraps any error

{nil}
  • go
  • golang
  • Понравилась статья? Поделить с друзьями:

    Читайте также:

  • Gdrv3 sys как исправить
  • Golang error format
  • Gboard что это как исправить
  • Gogs go error loading module requirements
  • Gateway authentication error vpn shrew

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии