В Swift 5 и новее какой тип имеет результат try? для бросающей функции, возвращающей Int?, и почему?

В Swift 5 и новее какой тип имеет результат try? для бросающей функции, возвращающей Int?, и почему?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Результат имеет тип Int?, а не Int??. Начиная со Swift 5, try? не добавляет дополнительный уровень optional, если вызываемое выражение уже возвращает optional.

Исторический контекст

Ранее try? оборачивал результат бросающего выражения в новый optional. Поэтому функция с результатом Int? могла приводить к типу Int??, где один уровень означал ошибку, а другой — успешное отсутствие значения.

В Swift 5 это поведение изменили, чтобы уменьшить неожиданную вложенность optional и сделать try? удобнее для типичных случаев. Такое упрощение улучшило читаемость, но устранило возможность различать ошибку и успешный результат nil только по типу optional.

Постановка проблемы

У функции могут быть два разных исхода: она успешно завершилась и вернула nil либо завершилась ошибкой. После применения try? оба исхода представлены одним значением nil, поэтому причина отсутствия результата теряется.

Это допустимо, если ошибка несущественна и нужно лишь получить значение при успехе. Если ошибка влияет на логику, журналирование или пользовательское сообщение, try? становится слишком грубым механизмом.

Подробное решение

try? преобразует ошибку в nil, а успешный результат оставляет доступным. Для функции, возвращающей Int?, Swift 5 и новее сохраняет тип Int?: успешный Int представлен как значение, успешный nil и ошибка — как nil.

enum ReadError: Error { case unavailable } func read(_ fail: Bool) throws -> Int? { if fail { throw ReadError.unavailable } return nil } let value = try? read(false) // Int?, успешный nil let detailed: Result<Int?, Error> = Result { try read(true) }

Если нужно различать три состояния — значение, успешное отсутствие значения и ошибку, — следует использовать Result<Int?, Error> или явный do-catch. В Result успешный nil находится в success, а ошибка — в failure, поэтому информация сохраняется.

Важное ограничение: try? не предназначен для анализа причины ошибки. Он удобен на необязательных этапах, например при попытке прочитать необязательную настройку, но не на границе доменной операции, где разные сбои требуют разных решений.

Ситуация из практики

Сервис читает значение из локального кэша. Отсутствие записи означает обычный cache miss, а ошибка хранилища означает повреждение данных или недоступность диска.

Вариант с try? краток и позволяет продолжить работу при любом отсутствии значения, но смешивает cache miss с отказом хранилища. Вариант с do-catch сохраняет причины ошибки, однако требует локальной обработки и может связывать инфраструктурный слой с логикой вызывающего кода.

Оптимальным решением на границе слоя будет Result<Значение?, Ошибка>. Он различает успешный nil и failure, позволяет передать ошибку выше без немедленного перехвата и оставляет вызывающему коду выбор стратегии восстановления. Если ошибка действительно неотличима от отсутствия значения, тогда try? будет более простым и оправданным выбором.

Что кандидаты часто упускают

  1. Можно ли утверждать, что try? всегда скрывает только ошибку?

Нет. Он также не позволяет отличить ошибку от успешного результата nil, если исходная функция возвращает optional. Поэтому после try? значение nil само по себе не сообщает, какой именно сценарий произошёл.

  1. Как сохранить различие между успешным nil и ошибкой без ручного do-catch?

Нужно использовать Result с optional-типом успеха: Result<Int?, Error>. Тогда success(nil) означает корректное отсутствие значения, а failure(error) — фактический сбой. Это особенно полезно при передаче результата между слоями приложения.

  1. Почему изменение поведения try? не означает, что optional больше нельзя вкладывать?

Вложенные optional по-прежнему могут существовать в обычных типах Swift. Изменился только способ формирования результата оператором try?: он не создаёт лишний уровень для уже optional-результата. Если модель действительно требует различать несколько уровней отсутствия значения, это нужно выразить отдельным типом или явной моделью состояния, а не полагаться на try?.