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

Значение или nil

Чему вы научитесь

Чему вы научитесь:

  • объяснять, что такое опционал и почему Int("42") даёт Int?, а не Int;
  • извлекать значение из опционала через if let и guard let;
  • подставлять значение по умолчанию через ?? и читать значение через ?.;
  • объяснять, чем опасно принудительное извлечение ! и когда оно допустимо.
Две подарочные коробки Int? с поднятыми крышками — в одной плитка 42, в другой карточка nil

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

Программе такие коробки достаются постоянно. Вспомните преобразование типов: Int(2.9) всегда даёт целое число 2. А что, если превращать в число текст?

В тексте "42" число записано, а в тексте "forty-two" его нет. Swift не угадывает и не останавливает программу. Он отвечает честно — коробкой, которая может оказаться пустой:

SWIFT
let first = Int("42")
let second = Int("forty-two")
print(type(of: first))             // Optional<Int>
print(first == nil, second == nil) // false true

Давайте разберём программу по строкам:

  1. Int("42") находит в тексте число 42 и кладёт его в коробку first.
  2. В тексте "forty-two" числа нет, и Int(…) возвращает пустую коробку. Пустоту в Swift записывают словом nil.
  3. print(type(of: first)) печатает тип коробки — Optional<Int>, а не Int.
  4. Сравнение с nil отвечает на вопрос «коробка пуста?»: first не пуста, а second пуста.

Тип Int?

Опционал хранит либо значение определённого типа, либо nil — «ничего». Тип опционала пишут как обычный тип со знаком вопроса: Int? читается как «Int или ничего». Полное имя того же типа — Optional<Int>, его и показал type(of:).

Знак вопроса ставят после любого типа: Double?, String?, Bool?. Форма у коробки-опционала одна: в Int? лежит либо целое число, либо ничего, но никогда не текст.

Откуда берутся опционалы

Результат, которого может не быть

Int(…) от текста — первый пример. Действие может не получиться, и тогда результата нет. Swift сообщает об этом через тип: раз результат — Int?, значит, его может и не быть. Читая код, вы сразу видите, где нужна проверка.

Значение, которого пока нет

Опционал объявляют и сами, когда значение появится позже. Например, лучший результат игрока неизвестен, пока не сыграна первая партия:

SWIFT
var bestScore: Int? = nil
print(bestScore == nil) // true
bestScore = 120
print(bestScore == nil) // false
  1. var bestScore: Int? = nil заводит пустую коробку для целого числа. Тип здесь пишут обязательно: по одному nil Swift не поймёт, какая коробка нужна.
  2. Первая проверка печатает true: коробка пуста.
  3. bestScore = 120 кладёт в коробку число. Отдельно упаковывать его не нужно: это Swift сделает сам.
  4. Вторая проверка печатает false.

Обычный Int так не умеет. Строка let count: Int = nil не скомпилируется: 'nil' cannot initialize specified type 'Int'. В Int всегда лежит число, а пустым может быть только опционал.

Int? — не Int

Коробка с числом — ещё не число. Попробуем прибавить к first единицу:

SWIFT
print(first + 1) // ✗
TEXT
error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'

Компилятор отказывается собирать программу. Сообщение говорит: значение типа Int? нужно извлечь, чтобы получить Int. Извлечение — это и есть открытие коробки: проверить, есть ли в ней значение, и достать его. Пока коробка закрыта, складывать нечего: вдруг там nil?

Здесь картинка с коробкой неточна. Настоящую коробку можно потрясти и угадать, что внутри. Компилятор угадывать не разрешает: даже когда вы уверены, что first не пуста, значение используют только после извлечения.

Зачем нужны опционалы

Этот вопрос часто задают на собеседовании: «Что такое опционал и какую ошибку он предотвращает?» Ответ состоит из двух частей:

  • опционал — тип вроде Int?, в котором лежит либо значение, либо nil;
  • компилятор не даёт использовать Int? как Int, пока значение не извлекли, поэтому забыть о пустом случае не получится.

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

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

Опционал напечатали, не извлекая значение:

SWIFT
print(first) // ✗
TEXT
warning: expression implicitly coerced from 'Int?' to 'Any'

Это предупреждение, поэтому программа запустится. Но она напечатает Optional(42), а не 42: print показывает коробку вместе с содержимым, а пустую коробку печатает словом nil. Сначала извлеките значение, а печатайте уже его.

Главное

  • Опционал Int? хранит либо значение типа Int, либо nil — «ничего».
  • Int("42") даёт Int?: в тексте может не оказаться числа, и тогда результат — nil.
  • Int? — не Int: пока значение не извлекли, компилятор не даст работать с ним как с числом.
  • nil может хранить только опционал, а если напечатать опционал без извлечения, компилятор выдаст предупреждение.