Внутри обработчика ошибки выполняется операция, которая тоже выбрасывает ошибку: какая ошибка выйдет из внешнего блока обработки?
Если код внутри catch выбрасывает новую ошибку, наружу распространяется именно новая ошибка; исходная автоматически не сохраняется. Внешний do-catch может перехватить её, если текущая функция допускает дальнейшее распространение ошибок.
Модель обработки ошибок Swift разделяет обычный путь выполнения и аварийный: функция сообщает о возможности сбоя через throws, а вызывающий код решает, где обработать ошибку. Такой подход позволяет не смешивать успешные значения и ошибки в каждом возвращаемом результате.
do-catch задаёт область, в которой ошибка перехватывается. Обработчик catch не является гарантированно безопасным: восстановление после сбоя само может завершиться другим сбоем.
Типичный пример — обработчик пытается выполнить запасной сценарий: повторить запрос, прочитать резервный источник или преобразовать ошибку. Если этот сценарий тоже завершается через throw, первоначальная ошибка не будет автоматически передана дальше.
Невнимательная замена исходной ошибки может скрыть настоящую причину сбоя. Кроме того, если текущая функция не объявлена как throws, компилятор не позволит выпустить новую ошибку из catch без её локальной обработки.
Новая ошибка из catch ведёт себя как обычная ошибка, выброшенная из текущей функции. Она покидает обработчик и ищет внешний do-catch; соседние обработчики того же do для неё не запускаются повторно.
Минимальный пример:
После throw RecoveryError.unavailable исходная InitialError.invalid теряется как отдельно доступная причина. Если её важно сохранить, следует создать собственную ошибку-обёртку с полем вроде underlying, содержащим исходный Error, и выбросить эту обёртку.
Если новую ошибку нужно обработать на том же уровне, внутри catch требуется вложенный do-catch. Если же её нужно передать выше, функция должна поддерживать распространение ошибок через throws или сама преобразовать ошибку в обычный результат.
Сервис загружает данные из основного источника. При ошибке авторизации обработчик пытается обратиться к резервному источнику, но резервный источник также недоступен.
Можно просто выбросить ошибку резервного источника. Это сохраняет непосредственную причину неудачного восстановления, но скрывает первоначальный сбой и затрудняет диагностику всей цепочки.
Можно подавить вторую ошибку и повторно выбросить первую. Такой вариант сохраняет исходную причину, но теряет сведения о том, что резервный путь тоже отказал.
Наиболее информативный вариант — выбросить доменную ошибку с двумя составляющими: исходной причиной и ошибкой резервного источника. Это требует собственного типа моделирования ошибок, зато логирование и аналитика получают полную цепочку отказа, а вызывающий код по-прежнему работает с устойчивыми случаями этого доменного типа.
Может ли обработчик catch того же do перехватить ошибку, выброшенную предыдущим catch?
Нет. Обработчики одного do выбираются для ошибки, возникшей в его основном теле. Ошибка из тела catch уже покидает этот обработчик, поэтому для её перехвата нужен внешний или вложенный do-catch.
Что произойдёт, если из catch выбросить полученную там же исходную ошибку?
Наружу уйдёт тот же экземпляр ошибки, если выброшено сохранённое значение ошибки без замены. В этом случае причина не меняется, но сам обработчик фактически не добавляет нового контекста.
Разрешено ли выбрасывать ошибку из catch в невыбрасывающей функции?
Нет, если эта ошибка не перехвачена внутри самой функции. Компилятор требует либо объявить функцию как throws, либо обработать новую ошибку во вложенном do-catch, либо преобразовать результат в невыбрасывающий тип, например успешное или ошибочное значение Result.