Описание пакета error языка программирования Go

Пакет errors реализует функции для манипуляции ошибками

Функция New создает ошибки, содержащие только текстовое сообщение.

Ошибка e оборачивает другую ошибку, если тип e имеет один из методов:

Unwrap() error
Unwrap() []error

Если e.Unwrap() возвращает не nil ошибку w или срез, содержащий w, то говорится, что e оборачивает w. Возвращение nil ошибки из e.Unwrap() указывает на то, что e не оборачивает никакую ошибку. Недопустимо, чтобы метод Unwrap() возвращал срез, содержащий nil значение ошибки.

Легкий способ создать обернутые ошибки — вызвать fmt.Errorf и применить шаблон %w к аргументу ошибки:

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

Последовательное разворачивание ошибки создает дерево. Функции Is и As исследуют дерево ошибки, проверяя сначала саму ошибку, а затем дерево каждого из ее потомков по очереди (префиксный, глубинный обход).

См. https://go.dev/blog/go1.13-errors для более глубокого обсуждения философии оборачивания и когда следует оборачивать.

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

if errors.Is(err, fs.ErrExist)

лучше, чем

if err == fs.ErrExist

потому что первая будет успешной, если err оборачивает io/fs.ErrExist.

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

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

лучше, чем

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

потому что первая будет успешной, если err оборачивает *io/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

Переменные

var ErrUnsupported = New("unsupported operation")

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

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

errors.Is(err, errors.ErrUnsupported)

либо путем прямого оборачивания ErrUnsupported, либо путем реализации метода Is.

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

Функции

func As

func As(err error, target any) bool

As находит первую ошибку в дереве err, которая совпадает с target, и если такая найдена, устанавливает target в значение этой ошибки и возвращает true. В противном случае возвращает false.

Дерево состоит из err самой по себе, за которой следуют ошибки, полученные путем многократного вызова ее метода Unwrap() error или Unwrap() []error. Когда err оборачивает несколько ошибок, As проверяет err, за которой следует глубинный обход ее потомков.

Ошибка совпадает с target, если конкретное значение ошибки может быть присвоено значению, на которое указывает target, или если у ошибки есть метод As(any) bool, такой что As(target) возвращает true. В последнем случае метод As отвечает за установку target.

Тип ошибки может предоставить метод As, чтобы его можно было рассматривать как если бы он был другим типом ошибки.

As вызывает панику, если target не является не nil указателем на тип, реализующий error, или на любой интерфейсный тип.

Пример
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

func Is

func Is(err, target error) bool

Is сообщает, совпадает ли какая-либо ошибка в дереве err с target.

Дерево состоит из err самой по себе, за которой следуют ошибки, полученные путем многократного вызова ее метода Unwrap() error или Unwrap() []error. Когда err оборачивает несколько ошибок, Is проверяет err, за которой следует глубинный обход ее потомков.

Ошибка считается совпадающей с целевой, если она равна этой целевой или если она реализует метод Is(error) bool, такой что Is(target) возвращает true.

Тип ошибки может предоставить метод Is, чтобы его можно было рассматривать как эквивалентный существующей ошибке. Например, если MyError определяет

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

то Is(MyError{}, fs.ErrExist) возвращает true. См. syscall.Errno.Is для примера в стандартной библиотеке. Метод Is должен только поверхностно сравнивать err и цель и не вызывать Unwrap для обоих.

Пример
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

func Join

func Join(errs ...error) error

Join возвращает ошибку, которая оборачивает переданные ошибки. Любые значения nil ошибки отбрасываются. Join возвращает nil, если все значения в errs равны nil. Ошибка форматируется как конкатенация строк, полученных путем вызова метода Error каждого элемента errs, с новой строкой между каждой строкой.

Не nil ошибка, возвращенная Join, реализует метод Unwrap() []error.

Пример:

package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("first error")
	err2 := errors.New("second error")
	err3 := errors.New("third error")

	combinedErr := errors.Join(err1, err2, err3)
	fmt.Println(combinedErr)
}

func New

func New(text string) error

New возвращает ошибку, которая форматируется как заданный текст. Каждый вызов New возвращает уникальное значение ошибки, даже если текст идентичен.

Пример:

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("something went wrong")
	fmt.Println(err)
}
Пример
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

func Unwrap

func Unwrap(err error) error

Unwrap возвращает основную ошибку, обернутую в err, если она существует. Если err не оборачивает никакую ошибку, Unwrap возвращает nil.

Пример
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