try? и try!
Бывает, что отправителю всё равно, почему посылка вернулась: вернулась — значит, ничего не вышло. Тогда штамп можно не читать, а считать, что посылки нет. А бывает, что отправитель уверен: адрес верный, посылка дойдёт, и сетку можно не натягивать.
Для этих двух случаев у try есть два варианта: try? и try!. Оба вызывают функцию с throws без блока do. Возьмём перечисление ParseError и функцию parse и посмотрим на первый вариант:
enum ParseError: Error {
case empty, badFormat
}
func parse(_ text: String) throws -> Int {
if text.isEmpty { throw ParseError.empty }
guard let n = Int(text) else {
throw ParseError.badFormat
}
return n
}
let weight = (try? parse("12")) ?? 0
let broken = (try? parse("12a")) ?? 0
print(weight, broken) // 12 0try?: ошибка становится nil
Давайте разберём последние три строки:
try? parse("12")вызывает функцию. Ошибки нет, и результат — опционалInt?с числом12внутри.try? parse("12a")получает ошибкуbadFormat, но никуда её не передаёт: ошибка пропадает, а результатом становитсяnil.?? 0достаёт число из опционала или подставляет0, если тамnil.printпечатает12 0.
Из темы об опционалах вы знаете коробку, которая бывает пустой. try? превращает выброшенную ошибку в такую пустую коробку. Поэтому с результатом try? работают как с любым опционалом: через ??, через if let n = try? parse(text) или через guard let.
Скобки вокруг try? parse("12") здесь обязательны: без них try? захватит всё выражение справа от себя, вместе с ?? 0.
Здесь картинка с почтой неточна. Вернувшуюся посылку можно отложить и прочитать штамп позже. После try? причину не узнать никак: в nil её нет. Поэтому try? пишут, только когда важно одно — получилось или нет.
try!: ошибки быть не может
try! тоже обходится без do, но опционал не делает: результат — обычное значение.
let fixed = try! parse("5")
print(fixed * 3) // 15fixed получает число 5 типа Int, и программа печатает 15. Так пишут, только когда ошибки быть не может по смыслу программы: текст "5" записан прямо в коде, и в нём число.
А если ошибка всё же прилетит, программа аварийно завершится:
let typed = try! parse("") // ✗
print(typed)Fatal error: 'try!' expression unexpectedly raised an error: main.ParseError.emptyСообщение читается так: выражение с try! неожиданно выбросило ошибку. В конце названа сама ошибка: тип ParseError и вариант empty. main перед ними — имя программы, которую собрала команда swiftc. До print дело не дошло.
try! похож на ! у опционала: оба открывают коробку не глядя. Для данных снаружи — текста от пользователя, из файла или из сети — он не подходит.
Этот вопрос часто задают на собеседовании: чем отличаются try, try? и try!? Ответ:
tryпропускает ошибку дальше, кcatchили к тому, кто вызвал функцию, а результат остаётся обычным значением;try?превращает ошибку вnil, а результат — в опционал, и причина ошибки теряется;try!даёт обычное значение, а при ошибке аварийно завершает программу.
Частая ошибка
Значение по умолчанию дописали без скобок:
let total = try? parse("x") ?? 0 // ✗warning: left side of nil coalescing operator '??' has non-optional type 'Int', so the right side is never usedЭто предупреждение, а не ошибка компиляции: программа соберётся, но работает не так, как задумано. try? действует на всё выражение справа от себя, а не на один вызов. Swift читает строку как try? (parse("x") ?? 0):
??получает отparseобычныйInt, поэтому0никогда не понадобится;- в
totalпопадает опционал, и при ошибке тамnil, а не0.
Исправление — скобки вокруг вызова: (try? parse("x")) ?? 0.
Главное
try?превращает ошибку вnil, а результат — в опционал; причина ошибки при этом теряется.- Значение по умолчанию добавляют через
??, аtry?с вызовом берут в скобки:(try? parse(text)) ?? 0. try!даёт обычное значение, а на ошибке аварийно завершает программу:Fatal error: 'try!' expression unexpectedly raised an error.try!пишут, только когда ошибки быть не может по смыслу программы; для данных снаружи подходятtryсcatchилиtry?.