Перейти к содержимому
Теория · Ошибки · урок 4 из 7

try? и try!

Бывает, что отправителю всё равно, почему посылка вернулась: вернулась — значит, ничего не вышло. Тогда штамп можно не читать, а считать, что посылки нет. А бывает, что отправитель уверен: адрес верный, посылка дойдёт, и сетку можно не натягивать.

Для этих двух случаев у try есть два варианта: try? и try!. Оба вызывают функцию с throws без блока do. Возьмём перечисление ParseError и функцию parse и посмотрим на первый вариант:

SWIFT
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 0

try?: ошибка становится nil

Давайте разберём последние три строки:

  1. try? parse("12") вызывает функцию. Ошибки нет, и результат — опционал Int? с числом 12 внутри.
  2. try? parse("12a") получает ошибку badFormat, но никуда её не передаёт: ошибка пропадает, а результатом становится nil.
  3. ?? 0 достаёт число из опционала или подставляет 0, если там nil.
  4. print печатает 12 0.

Из темы об опционалах вы знаете коробку, которая бывает пустой. try? превращает выброшенную ошибку в такую пустую коробку. Поэтому с результатом try? работают как с любым опционалом: через ??, через if let n = try? parse(text) или через guard let.

Скобки вокруг try? parse("12") здесь обязательны: без них try? захватит всё выражение справа от себя, вместе с ?? 0.

Здесь картинка с почтой неточна. Вернувшуюся посылку можно отложить и прочитать штамп позже. После try? причину не узнать никак: в nil её нет. Поэтому try? пишут, только когда важно одно — получилось или нет.

try!: ошибки быть не может

try! тоже обходится без do, но опционал не делает: результат — обычное значение.

SWIFT
let fixed = try! parse("5")
print(fixed * 3) // 15

fixed получает число 5 типа Int, и программа печатает 15. Так пишут, только когда ошибки быть не может по смыслу программы: текст "5" записан прямо в коде, и в нём число.

А если ошибка всё же прилетит, программа аварийно завершится:

SWIFT
let typed = try! parse("") // ✗
print(typed)
TEXT
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! даёт обычное значение, а при ошибке аварийно завершает программу.

Частая ошибка

Значение по умолчанию дописали без скобок:

SWIFT
let total = try? parse("x") ?? 0 // ✗
TEXT
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?.