Что позволяет пользовательскому типу ошибки изменить результат errors.As без совпадения конкретного типа?
Пользовательский тип ошибки может реализовать метод As(any) bool. Он получает указатель target, переданный в errors.As, и может самостоятельно записать в него подходящее значение; если метод возвращает true, поиск завершается успешно даже без обычной совместимости типов.
До появления стандартных механизмов errors.Is и errors.As в Go 1.13 обработка ошибок часто зависела от прямого приведения типов и соглашений конкретного проекта. Это затрудняло добавление контекста к ошибкам: обёртка скрывала исходный тип, а вызывающий код был вынужден знать детали реализации.
Метод As стал расширением стандартного поиска. Он позволяет типу ошибки представить себя как другой совместимый по смыслу тип или интерфейс, не раскрывая внутреннюю структуру ошибки.
Один пакет может возвращать внутреннюю ошибку, которая не должна становиться частью публичного API. При этом вызывающему коду нужно предоставить стабильное поведение, например интерфейс с методом получения кода или диагностической информации.
Прямое приведение к внутреннему типу связывает вызывающий код с реализацией. Если обёртка не реализует требуемый внешний интерфейс, обычный поиск errors.As также не найдёт его, даже если ошибка логически предоставляет такое поведение.
При вызове errors.As стандартный алгоритм проходит ошибку и её обёртки. Для каждого значения он сначала проверяет, можно ли присвоить его целевому типу; затем, если тип предоставляет метод As(any) bool, вызывает этот метод. Успешное присваивание или возврат true завершает поиск.
Метод должен сам корректно проверить тип target и записать в него значение подходящего типа. Нельзя безусловно присваивать значение через небезопасное преобразование: нарушение контракта может привести к панике.
Минимальный пример:
Вызов успешен, хотя internalError не обязан быть экспортируемым: он присваивает себя интерфейсной цели Public. В реальном примере потребуется импорт errors; здесь показан только механизм метода As.
Целевой аргумент errors.As должен быть ненулевым указателем на тип ошибки или на интерфейс, который реализуют ошибки. Сам метод As не должен рекурсивно вызывать errors.As для той же ошибки, иначе можно получить бесконечную рекурсию.
As следует применять осторожно. Он расширяет публичный контракт ошибки: после начала использования вызывающий код может рассчитывать на соответствующее представление, поэтому изменение или удаление такой поддержки способно нарушить совместимость.
Внутренний HTTP-клиент возвращает ошибки разных реализаций: одна содержит код удалённого сервиса, другая — код локальной валидации. Внешнему слою нельзя экспортировать эти структуры, но ему нужно единообразно получать диагностический интерфейс.
Вариант с экспортом всех конкретных типов прост для реализации, но жёстко связывает API с внутренними структурами и затрудняет рефакторинг. Вариант с ручной проверкой кодов через текст ошибки непредсказуем и ломается при изменении сообщений.
Выбран интерфейс Public и контролируемая реализация As. Внутренние типы остаются заменяемыми, а внешний слой зависит только от стабильного поведения; результатом становится более устойчивый контракт без анализа строк.
Нет, это некорректный контракт. Возврат true сообщает errors.As, что совпадение найдено, поэтому функция завершит поиск и вернёт true; если значение в target не записано, вызывающий код получит ложный успешный результат и может обратиться к нулевому значению.
Нет. Как только обычная проверка типа или пользовательский As успешно срабатывает, обход прекращается. Поэтому внешняя ошибка может перехватить поиск раньше вложенной, а порядок обёрток становится частью наблюдаемого поведения.
Нет. Unwrap описывает переход к исходной ошибке и сохраняет возможность дальнейшего обхода цепочки. As предоставляет альтернативное представление текущей ошибки для конкретной цели; он не обязан раскрывать причину и не заменяет распространение ошибки через Unwrap.