В публичном API результат операции должен передаваться как значение успеха или ошибки без немедленного перехвата: какой механизм Swift подходит для этого случая?
Подходит Result — перечисление, которое явно хранит либо успешное значение, либо ошибку. Оно передаёт исход операции как обычное значение, поэтому вызывающий код сам выбирает момент и способ обработки результата.
В отличие от throws, Result не распространяет ошибку по стеку вызовов автоматически: её нужно разобрать через сопоставление с вариантами или методы самого типа.
До появления стандартного Result асинхронные API часто сообщали об успехе и ошибке через completion-обработчики с несколькими параметрами. Такой подход допускал неоднозначные состояния: например, одновременно переданные значение и ошибка или отсутствие обоих значений.
Стандартный Result появился в Swift 5 как типобезопасный способ представить взаимоисключающие исходы операции. Он особенно удобен на границах API, где результат нужно сохранить, передать дальше или обработать не сразу.
Иногда функция не должна немедленно прерывать управление при ошибке. Результат может поступить из callback, быть сохранён в кэше, передан между слоями приложения или объединён с другими результатами.
Использование throws в такой ситуации может привести к лишним обёрткам и немедленному do-catch. Неявные соглашения вроде отдельного значения ошибки хуже тем, что не гарантируют взаимоисключение успеха и сбоя на уровне типов.
Result имеет два обобщённых параметра: тип успешного значения Success и тип ошибки Failure, который должен соответствовать протоколу Error. Экземпляр всегда находится ровно в одном состоянии: .success или .failure.
В примере ошибка не выбрасывается и не перехватывается автоматически. switch гарантирует обработку обоих вариантов, а конкретный тип NetworkError позволяет обращаться с ошибками программно, без разбора текстового сообщения.
Для преобразования результата применяются map, mapError и flatMap. map меняет успешное значение, сохраняя ошибку; mapError преобразует тип ошибки; flatMap позволяет последовательно соединять операции, возвращающие Result.
Метод get() преобразует Result в механизм throws: он возвращает успешное значение или выбрасывает сохранённую ошибку. Это удобно на границе между кодом, который накапливает результат как значение, и кодом, использующим обычное распространение ошибок.
Главное ограничение — Result не делает обработку ошибки автоматической. Если вызывающий код просто передаёт значение дальше или игнорирует его, ошибка может быть обработана слишком поздно или потеряна логически. Кроме того, Result не заменяет async: он описывает исход операции, но не её асинхронность.
Выбор обычно определяется границей API. Для синхронной последовательной логики часто проще throws; для хранения, передачи и композиции исхода операции — Result; для современного асинхронного кода возможна комбинация async throws, если результат не требуется представлять как значение.
Сервис загрузки данных должен передать итог операции из сетевого callback в несколько слоёв приложения. Рассматривались три варианта: два optional-параметра в callback, throws внутри синхронной обёртки и Result.
Два optional-параметра просты для быстрого кода, но допускают некорректные состояния: оба параметра могут быть заполнены или оба могут быть nil. throws хорошо подходит для последовательного синхронного вызова, но сам по себе не является контейнером, который можно безопасно сохранить и передать как единое значение.
Был выбран Result<Value, ServiceError>. Он сделал успех и ошибку взаимоисключающими на уровне типа, позволил передавать единый объект между слоями и обеспечил явную обработку исхода в месте, где принимается бизнес-решение. Для асинхронного ожидания при этом отдельно использовался механизм async.
Result с любым типом ошибки?Нет. Параметр Failure должен соответствовать Error. Это позволяет стандартным операциям вроде get() корректно преобразовывать неуспех в выбрасываемую ошибку и сохраняет единый контракт для обработки сбоев.
При этом Result не требует, чтобы ошибка была конкретным перечислением. Можно использовать собственный тип ошибки, композиционный тип или any Error, но чем точнее тип, тем больше статической информации получает вызывающий код.
Result принципиально отличается от throws, если оба описывают успех и ошибку?throws является механизмом управления потоком: ошибка автоматически передаётся вверх по цепочке вызовов, пока её не обработают или не передадут дальше. Result является значением, которое можно сохранить, передать, сопоставить с вариантами и обработать позже.
Из-за этого throws обычно компактнее для линейной логики, а Result выразительнее на границах, где исход операции должен существовать независимо от текущего стека вызовов. Один механизм не делает другой устаревшим: выбор зависит от формы взаимодействия API.
Result дальше без проверки?Ничего автоматически не произойдёт: ошибка не будет выброшена только из-за того, что она находится в .failure. Она продолжит существовать как часть значения, пока код не сопоставит варианты, не вызовет преобразующий метод или не применит get().
Это даёт контроль, но создаёт ответственность. На важных границах следует явно определить, где результат разбирается, как ошибка преобразуется для следующего слоя и может ли операция завершиться без реакции на .failure.