Как передать ошибку вызывающему коду, не обрабатывая её на текущем уровне?
Если текущая функция сама объявлена как throws, вызов другой выбрасывающей функции нужно выполнить с try, но не заключать в локальный do-catch. Возникшая ошибка автоматически покинет текущую функцию и передастся её вызывающему коду.
В Swift обработка ошибок отделена от обычного возвращаемого значения. Такой подход позволяет явно обозначить в сигнатуре функции возможность сбоя и не заставляет каждый промежуточный уровень немедленно принимать решение о том, как его обработать.
Модель особенно полезна для многоуровневых приложений: нижний слой сообщает об ошибке, промежуточные слои могут её безопасно передать, а граница приложения преобразует её в пользовательское сообщение или другой результат.
Предположим, репозиторий загружает данные, сервис применяет бизнес-правила, а экран отображает результат. Если сервис перехватит ошибку только потому, что обязан вызвать throws-функцию, он может потерять исходную причину или преждевременно связать инфраструктурную ошибку с интерфейсом.
Неверное использование try? превращает любую ошибку в nil, а try! приводит к аварийному завершению при сбое. Поэтому промежуточный уровень должен уметь не обрабатывать ошибку, а корректно делегировать её выше.
Вызов с try только разрешает операции передать ошибку наружу; сам по себе try её не перехватывает. Если вызывающая функция имеет throws, ошибка немедленно завершает текущую функцию и становится результатом её неуспешного завершения.
В примере prepareValue не знает, как исправить сбой, поэтому объявлена как throws. Ошибка проходит через неё и обрабатывается в do-catch на внешнем уровне.
Это отличается от try?, который преобразует ошибку в отсутствие значения, и от try!, который предполагает отсутствие ошибки и аварийно завершает выполнение при нарушении этого предположения. Если текущий уровень действительно владеет контекстом обработки, он может использовать do-catch, при необходимости преобразовав ошибку в доменный тип.
Обязательное условие — вызывающая функция также должна иметь способ завершиться без обычного значения: объявить throws, обработать ошибку через do-catch либо явно выбрать try? или try!. Простое добавление try не делает функцию безопасной и не устраняет необходимость определить границу обработки.
Сервис оформления заказа вызывает репозиторий платежей. Репозиторий может сообщить, что сеть недоступна, платёж отклонён или запрос истёк по времени.
Рассматривались три варианта:
try? в сервисе — не приводит к аварийному завершению, но превращает разные причины в одинаковое отсутствие результата.throws — сохраняет тип и контекст, но требует явно определить точку окончательной обработки.Выбран третий вариант: репозиторий и сервис пробрасывают ошибку, а граница приложения преобразует известные причины в состояния интерфейса. В результате технические детали не смешиваются с UI, а неожиданные ошибки не маскируются под обычное отсутствие данных.
Вопрос: Можно ли пробросить ошибку из функции, которая сама не объявлена как throws?
Ответ: Нет, если речь идёт о нормальном вызове выбрасывающей функции. Такая функция должна либо обработать ошибку внутри do-catch, либо изменить свою сигнатуру и объявить throws. Исключения составляют явные преобразования через try? и try!, но они меняют семантику: первое скрывает ошибку за nil, второе переносит ответственность за отсутствие ошибки на разработчика.
Вопрос: Сохраняет ли проброшенная ошибка свой исходный тип?
Ответ: При обычном пробросе ошибка передаётся как значение, соответствующее протоколу Error, без обязательного преобразования в новый тип. Поэтому внешний catch может сопоставить её с конкретным типом или использовать общий обработчик. Если промежуточный слой сознательно оборачивает ошибку в собственный доменный тип, исходную причину следует сохранить отдельно, если она нужна для диагностики или последующего анализа.
Вопрос: Что происходит с выполнением текущей функции после того, как try обнаружил ошибку?
Ответ: Обычное выполнение функции прекращается в точке неуспешного вызова, а управление передаётся ближайшему подходящему обработчику выше по стеку. Инструкции после этого вызова в текущей функции не выполняются. Зарегистрированные ранее блоки defer при выходе из функции выполняются перед фактическим возвратом ошибки, поэтому их используют для освобождения ресурсов и восстановления инвариантов, но не как замену обработке ошибки.