Объясните механизм rethrows: когда функция с этим модификатором может завершиться ошибкой?
Функция с модификатором rethrows может выбросить ошибку только как результат вызова переданной ей функции, которая сама объявлена как throws, либо другой функции с совместимым поведением rethrows. Если все переданные функции не выбрасывают ошибки, вызов такой функции не требует обработки ошибки через try.
rethrows не означает, что функция всегда может выбрасывать произвольную ошибку: она ограничивает источник ошибок параметрами-функциями.
Высокоуровневые функции часто принимают замыкания: например, выполняют действие над элементами коллекции или передают значение в пользовательский обработчик. Без rethrows такую функцию пришлось бы объявлять как безусловно выбрасывающую ошибку через throws, даже если конкретный переданный обработчик ошибки не выбрасывает.
rethrows позволяет сохранить точный контракт: функция лишь передаёт наружу ошибку из переданного вызываемого параметра, но не становится источником произвольных ошибок сама по себе.
Рассмотрим функцию, которая вызывает переданное замыкание. Одни замыкания могут завершиться ошибкой, другие — нет. Если объявить обёртку обычной throws, каждый её вызов потребует try, даже при безопасном замыкании.
Если объявить её невыбрасывающей, она не сможет корректно передать ошибку от замыкания. Ошибочная классификация контракта ухудшает API: либо появляются ненужные проверки ошибок, либо обработка ошибок становится невозможной.
Модификатор rethrows разрешён для функции, у которой есть параметр-функция или параметр-замыкание с потенциально выбрасывающим вызовом. Компилятор анализирует, какой вариант передан при конкретном вызове:
rethrows-функции не требует try;try;rethrows-функции;rethrows-функция не должна безусловно выбрасывать произвольные ошибки независимо от параметров.В первом вызове переданное замыкание имеет невыбрасывающий фактический тип, поэтому try не нужен. Во втором замыкание может выбросить ошибку, и вызывающий код обязан использовать try и обработать или передать ошибку дальше.
Главное ограничение — rethrows не является более слабой формой throws. Оно выражает зависимость выбрасывающего поведения от параметра. Если функция должна сама проверять условие и выбрасывать собственную ошибку независимо от переданного замыкания, нужен обычный throws.
Практический компромисс состоит в том, что API с rethrows точнее, но его контракт сложнее читать: необходимость try зависит от фактического поведения переданных аргументов. Тем не менее это позволяет компилятору сохранять статическую проверку ошибок без лишнего шума в безопасных сценариях.
Допустим, библиотечная функция принимает обработчик завершения операции. В простом сценарии обработчик только обновляет состояние, но в другом сценарии он может выполнять разбор данных и выбрасывать ошибку.
Вариант с обычным throws проще для реализации, но заставляет каждый вызов обрабатывать ошибку, даже когда обработчик безопасен. Вариант без throws не позволяет передать ошибку от обработчика и вынуждает использовать отдельный результат или ручное преобразование ошибок.
Выбранное решение — rethrows, если функция действительно только вызывает переданный обработчик и передаёт его ошибку наружу. Это сохраняет естественный вызов для невыбрасывающих обработчиков и не скрывает ошибки для выбрасывающих.
Если же функция сначала валидирует собственные входные данные и может выбросить ошибку ещё до вызова обработчика, следует использовать обычный throws либо разделить API на две функции. Иначе rethrows будет описывать контракт неточно.
1. Может ли rethrows-функция выбросить собственную ошибку, не связанную с параметром?
Нет, это нарушает смысл контракта rethrows. Ошибка должна быть получена через вызов разрешённого выбрасывающего параметра или совместимой rethrows-функции. Для независимых собственных ошибок используется обычный throws.
2. Всегда ли при вызове rethrows-функции требуется try?
Нет. Требование определяется конкретными аргументами. Если переданное замыкание не может выбрасывать ошибку, вызов может быть выполнен без try; если оно имеет выбрасывающий тип, нужен try.
3. Чем rethrows отличается от возврата ошибки через Result?
rethrows использует встроенный механизм распространения ошибок Swift: вызывающий код применяет try, а обработка выполняется через do-catch или передаётся выше. Result делает ошибку обычным значением, поэтому её можно хранить, передавать и обрабатывать как данные, но это меняет стиль API и не даёт тех же правил статической проверки throws.