Перейти к содержимому
Теория · Опционалы · урок 4 из 8

guard let

Представьте, что вы собираетесь в гости и первым делом заглядываете в коробку с подарком. Если она пуста, идти незачем и вы сразу отменяете поход. Если подарок на месте, вы берёте его с собой и больше о проверке не думаете.

Имя из if let живёт только внутри фигурных скобок, поэтому весь код, которому нужно значение, приходится писать внутри них. С каждой новой проверкой он уходит всё дальше вправо. Функции удобнее другой порядок: проверить всё в самом начале и сразу выйти, если чего-то не хватает. Для этого в Swift есть guard:

SWIFT
func greeting(_ text: String) -> String {
    guard let age = Int(text) else {
        return "not a number"
    }
    guard age >= 0 else {
        return "negative age"
    }
    return "next year you turn \(age + 1)"
}
print(greeting("30"))  // next year you turn 31
print(greeting("abc")) // not a number
print(greeting("-5"))  // negative age

Давайте разберём три вызова:

  1. greeting("30"): в тексте есть число, первый guard пропускает дальше, и константа age равна 30. Второй guard тоже пропускает: 30 не меньше нуля. Функция возвращает next year you turn 31.
  2. greeting("abc"): числа нет, Int(text) даёт nil, и выполняется else первого guard. return сразу завершает функцию, и до остальных строк дело не доходит.
  3. greeting("-5"): число есть, но второй guard видит, что оно меньше нуля, и его else возвращает negative age.

Как устроена запись

Строка guard let age = Int(text) else { … } состоит из трёх частей:

  • guard — «убедись, что…»;
  • let age = Int(text) — та же привязка опционала, что в if let;
  • else { … } — блок, который выполняется, только когда в коробке nil.

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

Что даёт guard

Имя живёт после guard

У if let имя существует внутри фигурных скобок, а у guard let — после всей конструкции, до конца блока, где стоит guard. Чаще всего это вся функция. В примере константа age объявлена в первом guard, а используется и во втором guard, и в последнем return.

else обязан выйти

Пройти ниже guard можно только со значением, поэтому else должен уйти и не пустить выполнение дальше. Из знакомых вам команд для этого подходят:

  • в функции — return;
  • в цикле — continue, чтобы перейти к следующей итерации, или break, чтобы завершить цикл.

Здесь картинка с подарком неточна. В жизни, увидев пустую коробку, можно махнуть рукой и всё равно пойти в гости. guard так не разрешает: если else не выходит, программа не скомпилируется.

guard с обычным условием

После guard может стоять не только let, но и любое условие типа Bool, как второй guard в примере. Если условие ложно, выполняется else. В цикле так отбрасывают неподходящие значения:

SWIFT
for n in 1...6 {
    guard n % 3 != 0 else {
        continue
    }
    print(n, terminator: " ") // 1 2 4 5
}

guard читается как «нужно, чтобы n не делилось на 3». Для 3 и 6 условие ложно и continue переходит к следующему числу, не доходя до print.

guard let или if let

Этот вопрос часто задают на собеседовании: «Чем guard let отличается от if let?» Отличия удобно держать в таблице:

if letguard let
Где живёт имявнутри фигурных скобокпосле guard, до конца блока
Ветка elseнеобязательна, может делать что угоднообязательна и должна выйти
Когда удобензначение нужно в одном местепроверка в начале функции

Выбирайте guard, когда без значения продолжать нет смысла, а if let — когда у программы есть осмысленный путь и для nil.

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

В else напечатали сообщение, но не вышли:

SWIFT
func showAge(_ text: String) {
    guard let age = Int(text) else {
        print("not a number")
    } // ✗
    print(age)
}
TEXT
error: 'guard' body must not fall through, consider using a 'return' or 'throw' to exit the scope

Программа не компилируется: после print выполнение пошло бы ниже guard, а там age без значения. В подсказке компилятора есть знакомый вам способ выйти — return. Допишите его после print("not a number"). В функции без -> инструкцию return пишут без значения: она только завершает функцию.

Главное

  • guard let открывает опционал, а имя из него живёт после guard до конца блока — чаще всего функции.
  • Ветка else у guard обязательна и должна выйти: например, через return в функции, через continue или break в цикле.
  • guard проверяет и обычное условие: при ложном выполняется else.
  • guard удобен для проверок в начале функции: основной код идёт ниже без вложенных скобок.