Разберите порядок обхода причин, который использует errors.Is для ошибки с несколькими вложенными причинами.
errors.Is проверяет сначала саму текущую ошибку, затем рекурсивно обходит её причины в глубину. Если ошибка раскрывается через Unwrap() []error, причины проверяются в порядке элементов этого среза; при совпадении поиск завершается.
Поэтому при нескольких подходящих причинах результат errors.Is будет true, но порядок важен для побочных эффектов пользовательского метода Is и для связанных механизмов поиска типа.
Изначально стандартная модель wrapping в Go описывала одну цепочку причин через Unwrap() error. Этого недостаточно, когда одна операция агрегирует результаты нескольких независимых подопераций.
Для таких случаев в Go появился многопричинный wrapping: ошибка может раскрывать несколько причин через Unwrap() []error, как это делает errors.Join. Стандартные функции обработки ошибок должны были сохранить единый механизм поиска и для цепочек, и для дерева причин.
Предположим, несколько операций завершились ошибками, причём одна из причин вложена в другую или несколько причин имеют общий тип. Важно понимать, в каком порядке errors.Is проверяет узлы, иначе можно неверно оценить результат пользовательской логики сопоставления.
Неверное предположение о порядке особенно опасно, если метод Is написан с побочными эффектами или если разработчик пытается определить «главную» причину по первому найденному совпадению. Само булево значение обычно не зависит от того, какая из нескольких причин совпала, но поведение сопутствующей логики может зависеть.
Алгоритм работает так:
Is текущей ошибки.Unwrap().Unwrap() error) поиск продолжается по цепочке.Unwrap() []error) причины обходятся слева направо, рекурсивно и в глубину.true; если все ветви исчерпаны, возвращается false.Минимальный пример:
В этом примере errors.Is проверит саму объединённую ошибку, затем первую, вторую и третью причины. На второй причине будет найдено совпадение, поэтому третья ветвь уже не потребуется.
Порядок элементов влияет на порядок обхода, но не превращает errors.Join в механизм выбора приоритетной причины. Если требуется явно определить основную ошибку, это следует выразить отдельным контрактом API, а не полагаться на случайный порядок обхода.
Метод Is должен выполнять сопоставление, а не самостоятельно обходить цепочку или дерево: обход выполняет пакет errors. Реализация также не должна рассчитывать на вызов метода Is определённое число раз, поскольку внутренний алгоритм и структура ошибок могут измениться.
Сервис одновременно проверяет права доступа и состояние лимита. Обе проверки могут завершиться ошибкой, поэтому разработчик объединяет их через errors.Join, чтобы вызывающий код мог проверить каждую категорию через errors.Is.
Первый вариант — вручную выбрать одну ошибку и вернуть только её. Он прост и даёт понятный приоритет, но теряет информацию о других отказах. Второй вариант — объединить причины, а затем считать первую причину главной только потому, что она стоит первой; это сохраняет данные, но создаёт неявный и хрупкий контракт.
Выбранное решение — вернуть объединённую ошибку для машинной проверки категорий, а при необходимости отдельно хранить или формировать явно определённое описание основной причины. В результате вызывающий код видит все доступные причины, но бизнес-приоритет не зависит от внутреннего порядка обхода.
1. Меняется ли результат errors.Is, если две ветви содержат одинаковую целевую ошибку?
Нет, результатом будет true уже после первого совпадения. Вторая ветвь не нужна для получения булева результата и обычно не обходится. Поэтому нельзя использовать повторное совпадение как способ подсчёта причин.
2. Обходит ли errors.Is все ветви после найденного совпадения?
Нет. Поиск прекращается при первом успешном сопоставлении. Это означает, что пользовательские методы Is в последующих ветвях могут не вызываться, поэтому они не должны содержать обязательные побочные эффекты, журналирование или изменение состояния.
3. Гарантирует ли порядок обхода выбор наиболее подходящей причины?
Нет. Порядок обхода определяет последовательность поиска, но не вводит понятие приоритета ошибки. Если несколько причин подходят под один критерий, errors.Is сообщает только факт наличия совпадения; приоритет нужно проектировать отдельно — например, специальным типом результата или явным полем основной причины.