Ошибки и defer

Тип error: возврат, проверка и оборачивание

Содержание курса

Создание и оборачивание ошибок: errors.New, fmt.Errorf с %w и errors.Is

Самый простой способ создать ошибку — errors.New:

import "errors"

var ErrNotPositive = errors.New("число должно быть положительным")

errors.New возвращает значение типа error с фиксированным сообщением. Объявлять такие переменные на уровне пакета — стандартная практика: они называются сигнальными ошибками (sentinel errors) и позволяют сравнивать конкретные причины сбоя.

Но что если функция нашла ошибку глубже и хочет добавить контекст, не теряя оригинал? Здесь нужен fmt.Errorf с глаголом %w:

import "fmt"

func process(n int) error {
    err := validate(n)
    if err != nil {
        return fmt.Errorf("process: %w", err)
    }
    return nil
}

%w именно оборачивает ошибку: новый объект содержит текст "process: число должно быть положительным", но внутри хранит ссылку на исходный ErrNotPositive. Это не то же самое, что fmt.Sprintf — у него нет %w, и цепочка была бы потеряна.

Теперь вопрос: как вызывающий код определит, что внутри обёртки именно ErrNotPositive, а не другая ошибка? Прямое сравнение err == ErrNotPositive не пройдёт — err уже другой объект.

err := process(-1)
fmt.Println(err == ErrNotPositive)      // false — это обёртка
fmt.Println(errors.Is(err, ErrNotPositive)) // true — разворачивает цепочку

errors.Is(err, target) проходит по всей цепочке обёрток и возвращает true, как только находит target. Цепочка может быть сколь угодно длинной — errors.Is доберётся до корня.

Практический вывод: errors.New создаёт «якорь», fmt.Errorf %w добавляет контекст на каждом уровне, errors.Is находит якорь в любой глубине обёртки.