Программирование GoОбработка ошибокGo-разработчик серверных приложений

Что вернёт errors.Is, если искомая ошибка равна nil, а проверяемая ошибка ненулевая и имеет собственный мет...

Что вернёт errors.Is, если искомая ошибка равна nil, а проверяемая ошибка ненулевая и имеет собственный метод Is?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

errors.Is вернёт false. Если искомая ошибка равна nil, функция сначала проверяет, равна ли nil сама проверяемая ошибка, и не вызывает пользовательский метод Is ненулевой ошибки.

Исторический контекст

В Go 1.13 появились стандартные механизмы проверки и обёртывания ошибок — errors.Is, errors.As и поддержка %w. Они решают проблему анализа цепочек ошибок без сравнения текстовых сообщений и без привязки вызывающего к конкретной реализации обёртки.

При этом у errors.Is есть специальные базовые случаи, которые обрабатываются до обхода цепочки и вызова пользовательских методов. Проверка nil — один из таких случаев.

Постановка проблемы

Разработчик может ожидать, что errors.Is всегда будет проходить по цепочке и вызывать Is у текущей ошибки. Это неверно для nil-цели: пользовательский метод не может изменить смысл проверки и объявить ненулевую ошибку совпадающей с nil.

Если перепутать errors.Is(err, nil) с проверкой категории ошибки, можно получить ошибочную логику обработки. Для определения специального класса ошибок нужна ненулевая sentinel-ошибка или типизированная цель, а не nil.

Подробное решение

Алгоритм errors.Is концептуально начинается с особой проверки цели. Если цель равна nil, результат определяется простым сравнением проверяемой ошибки с nil:

  • errors.Is(nil, nil) возвращает true;
  • errors.Is(ненулеваяОшибка, nil) возвращает false;
  • метод Is ненулевой ошибки в этих случаях не вызывается.

Только при ненулевой цели начинается обычная обработка: сначала проверяется прямое совпадение, затем может быть вызван метод Is, после чего исследуются обёрнутые или объединённые причины.

Минимальная иллюстрация:

package main import ( "errors" "fmt" ) type specialError struct{} func (specialError) Error() string { return "special" } func (specialError) Is(error) bool { panic("этот метод не должен вызываться") } func main() { err := specialError{} fmt.Println(errors.Is(err, nil)) // false }

Метод Is предназначен для сопоставления с конкретной ненулевой категорией ошибки. Он не является механизмом переопределения нулевости интерфейса error и не должен использоваться для имитации nil.

Важно также отличать nil-интерфейс от интерфейса, содержащего типизированный nil-указатель. Последний сам по себе может быть ненулевым значением error, поэтому errors.Is(err, nil) способен вернуть false. Это связано с семантикой интерфейсов Go, а не с обходом цепочки ошибок.

Ситуация из практики

В HTTP-сервисе общий обработчик ошибок пытается определить отсутствие ошибки через errors.Is(err, nil). Позже в коде появляется пользовательский тип ошибки с методом Is, который сопоставляет её с несколькими категориями. Разработчик ожидает, что этот метод будет вызван и сможет повлиять на результат проверки с nil.

Рассматривались два варианта. Первый — разрешить пользовательским ошибкам самостоятельно трактовать nil; это нарушает предсказуемую семантику отсутствия ошибки и усложняет анализ кода. Второй — использовать errors.Is(err, nil) только как эквивалент проверки отсутствия ошибки, а категории проверять через ненулевые sentinel-ошибки или типы.

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

Что кандидаты часто упускают

  1. Вопрос: Вызывается ли метод Is проверяемой ошибки, если цель равна nil, но сама ошибка завернута в несколько слоёв?

    Ответ: Нет. Специальная проверка цели выполняется до обхода цепочки, поэтому ни внешняя обёртка, ни внутренние причины, ни их методы Is не анализируются. Для ненулевой цели обход, напротив, может продолжиться через Unwrap.

  2. Вопрос: Может ли errors.Is вернуть true для ненулевой ошибки и nil-цели из-за того, что одна из внутренних причин равна nil?

    Ответ: Нет. Наличие nil внутри цепочки не превращает всю цепочку в отсутствие ошибки. Обёртка остаётся ненулевой ошибкой, а errors.Is(обёртка, nil) возвращает false; nil-цель проверяет сам переданный корень, а не наличие пустой причины внутри него.

  3. Вопрос: Почему проверка err == nil обычно предпочтительнее для обычного определения отсутствия ошибки, хотя errors.Is(err, nil) даёт тот же логический результат?

    Ответ: err == nil прямо выражает намерение и не требует обхода цепочки или знания API errors. errors.Is(err, nil) корректен, но избыточен: его смысл особенно полезен для проверки ненулевых причин, категорий и типов. В прикладном коде err == nil обычно лучше читается как базовая проверка отсутствия ошибки.