В Swift 6 публичная функция объявлена с типизированным throws: какое ограничение на выбрасываемые ошибки получает вызывающий код?
Типизированный throws ограничивает контракт функции указанным типом ошибки: функция не может выбросить ошибку другого несовместимого типа. Благодаря этому вызывающий код знает тип ошибки статически и может обрабатывать его без приведения из общего Error.
Это ограничение относится к компилятору, а не к формату выполнения: ошибка по-прежнему распространяется через try, перехватывается в catch и не превращается автоматически в Result.
Обычный throws сообщает только факт возможного сбоя, но не его тип: вызывающий код получает ошибку, представленную через Error. Такой подход упрощает композицию функций, однако важная часть контракта остаётся невыраженной в сигнатуре.
Типизированные ошибки в Swift 6 решают проблему недостаточной статической информации. Разработчик может явно описать допустимый набор ошибок публичного API, сохранив модель управления потоком через исключения Swift.
Представим API хранилища, который документирован как возвращающий ошибки чтения, но технически может выбросить любую реализацию Error. Клиент не может надёжно проверить полноту обработки на уровне типов и вынужден либо использовать общий catch, либо делать приведения.
Если объявить слишком широкий тип ошибки, преимущество теряется: контракт формально становится безопасным, но малоинформативным. Если указать слишком узкий тип и позже начать выбрасывать новые причины сбоя, потребуется изменить API или явно преобразовать новые ошибки в существующую модель.
Сигнатура с типизированным throws задаёт разрешённый тип ошибки. Например, throws(StorageError) означает, что из функции может выйти только StorageError; попытка напрямую выбросить несовместимый тип будет отвергнута компилятором.
Тип ошибки становится частью статического контракта функции. Это помогает рефакторингу, автодополнению и проверке преобразований ошибок, но не означает, что компилятор автоматически потребует отдельную ветку для каждого случая перечисления: полноту switch нужно обеспечивать в тех местах, где значение действительно разбирается как перечисление.
Вложенный вызов также должен быть совместим с объявленным типом. Если вызываемая функция может выбросить более широкий Error, её ошибку сначала нужно обработать или преобразовать в StorageError; неявного сужения типа не происходит.
Компромисс состоит в связанности API с моделью ошибок. Типизированный контракт повышает точность, но изменение набора или типа ошибок может стать изменением публичного интерфейса. Для стабильных доменных ошибок это обычно полезно, а для низкоуровневых библиотек с большим числом деталей иногда практичнее использовать обёртку вроде одной публичной ошибки с сохранённой причиной.
Команда разрабатывает SDK для локального хранилища. Клиентам нужно различать отсутствие записи и повреждение данных, но внутренний слой также использует ошибки файловой системы.
Вариант с обычным throws прост для реализации и не связывает сигнатуру с конкретным перечислением, однако клиенты получают слабый контракт и вынуждены разбирать общий Error. Вариант с Result делает успех и ошибку явными значениями, но меняет стиль управления потоком и требует последовательно передавать Result через весь синхронный слой.
Выбран типизированный throws(StorageError) на границе SDK. Внутренние ошибки файловой системы преобразуются в случаи StorageError, а диагностическая исходная ошибка сохраняется отдельно в доменной обёртке. В результате клиент получает предсказуемый набор причин, а внутренние детали реализации не становятся частью публичного API.
throws обработку всех вариантов перечисления обязательной?Ответ: Нет. Он ограничивает тип выбрасываемой ошибки, но не заставляет каждый catch перечислять все случаи. Можно использовать общий catch, передать ошибку дальше или обработать только часть вариантов, если остальные покрываются последующим обработчиком. Полнота требуется прежде всего для switch по перечислению, а не для самой конструкции обработки ошибок.
throws(StorageError) вызов, который выбрасывает обычный Error?Ответ: Напрямую нельзя, потому что тип потенциально выбрасываемой ошибки шире разрешённого контракта. Сначала нужно перехватить исходную ошибку и преобразовать её в StorageError, либо изменить контракт внешней функции. Это предотвращает скрытое появление неподдержанных причин сбоя, но требует явных адаптеров на границах подсистем.
throws(Never) отличается от отсутствия throws?Ответ: Оба варианта описывают функцию, которая не выбрасывает ошибку, но throws(Never) остаётся типизированной формой бросающего контракта и может быть полезен в обобщённом коде, где тип ошибки выводится параметрически. Для обычного API отсутствие throws обычно проще и яснее. Типизированный вариант особенно ценен, когда одна и та же абстракция должна работать и с бросающими, и с гарантированно небросающими реализациями.