Ошибка должна предоставлять дополнительное поведение через интерфейс, но её конкретный тип неизвестен: каким механизмом Go это обнаружить?
Используйте errors.As с целью, объявленной как указатель на интерфейс. Механизм пройдёт по цепочке обёрнутых ошибок, найдёт первую ошибку, присваиваемую этому интерфейсу, и позволит вызвать его методы без знания конкретного типа.
До появления стандартных средств работы с цепочками ошибок обработчики часто применяли прямые приведения типов или анализировали текст сообщения. Такой подход был хрупким: обёртка скрывала исходный тип, а изменение формулировки сообщения ломало логику.
В Go 1.13 появились errors.Is, errors.As и соглашение об unwrap-цепочке. Они отделили машинно проверяемые свойства ошибки от её диагностического текста; errors.As предназначен именно для извлечения ошибки по типу или интерфейсу.
Конкретная ошибка может быть создана стандартной библиотекой, сторонним пакетом или одной из нескольких реализаций внутри приложения. Если обработчик жёстко проверяет конкретный тип, он становится связанным с реализацией и может перестать работать после добавления обёртки.
Проверка текста также ненадёжна: сообщение предназначено для диагностики человека, а не для стабильного программного контракта. Неверный выбор цели для errors.As приводит к тому, что интерфейс не будет найден, даже если ошибка фактически поддерживает нужное поведение.
Целью errors.As должна быть переменная, переданная по адресу. Если требуется найти ошибку, реализующую интерфейс, переменная объявляется этим интерфейсом, а в errors.As передаётся её адрес.
В примере errors.As проверяет саму ошибку и её обёртку. Когда находится значение, чьи методы удовлетворяют интерфейсу interface{ Timeout() bool }, оно присваивается переменной t, после чего вызывается t.Timeout().
Передача &t обязательна: функции нужно записать найденное значение в переменную. Использовать следует интерфейс, описывающий действительно необходимое поведение, например net.Error, а не избыточный конкретный тип.
errors.As учитывает стандартную цепочку через Unwrap; пользовательский тип должен корректно предоставлять этот метод, если он оборачивает другую ошибку. Если одна ошибка может содержать несколько причин, обход зависит от поддерживаемой структуры unwrap-цепочки, включая многозначный вариант, возвращающий набор ошибок.
Интерфейс должен быть достаточно узким и стабильным. Слишком общий интерфейс увеличивает число ложных совпадений, а слишком специфичный связывает обработчик с деталями реализации. Как и при любой проверке ошибки, найденное дополнительное поведение не гарантирует конкретную причину сбоя: оно сообщает только о наличии соответствующего контракта.
HTTP-клиент получает ошибку сетевого запроса, обёрнутую несколькими слоями. Сервису нужно понять, поддерживает ли ошибка поведение сетевой ошибки с признаком временного сбоя, но конкретный тип зависит от используемого транспорта.
Можно применить проверку конкретного типа. Она проста, но связывает код с реализацией транспорта и может перестать работать после замены библиотеки. Можно анализировать текст, однако локализация, изменение сообщений и отсутствие стабильного формата делают этот вариант ненадёжным.
Выбранное решение — искать согласованный интерфейс через errors.As, например net.Error, и затем использовать только нужные методы интерфейса. Это сохраняет совместимость с обёртками и разными реализациями; результатом становится корректное решение о повторе запроса без зависимости от текста и внутреннего типа ошибки.
Почему нельзя передать в errors.As саму интерфейсную переменную вместо её адреса?
errors.As должен изменить переменную, записав в неё найденную ошибку. Поэтому аргументом является указатель на целевую переменную: для переменной интерфейсного типа это указатель на интерфейс. Передача самой переменной не даёт функции места для записи и нарушает требование к аргументу target.
Что произойдёт, если целевой интерфейс слишком общий?
Будет возвращена первая ошибка в цепочке, которая ему соответствует. Например, интерфейс с методом Error() string фактически реализует любая ошибка, поэтому такой поиск почти не фильтрует причины и может выбрать внешний слой обёртки вместо полезной внутренней ошибки.
Целевой интерфейс должен содержать осмысленное дополнительное поведение: признак временности, код, категорию или другой стабильный контракт. Чем уже интерфейс, тем меньше риск случайного совпадения, но чрезмерная узость может исключить совместимые реализации.
Найдёт ли errors.As интерфейс, если ошибка была обёрнута через форматирование без сохранения причины?
Нет. Для обхода цепочки причина должна быть сохранена механизмом Unwrap, обычно через форматирование с %w или собственный метод Unwrap. Если причина превращена только в текст, связь с исходным значением утрачена, и errors.As не сможет извлечь из неё интерфейс.
Это важное проектное ограничение: диагностическое сообщение и структурированная причина — разные свойства ошибки. Текст можно сохранить для журналирования, но для программной обработки нужно явно сохранить исходную ошибку в unwrap-цепочке.