Перейти к содержимому
Теория · Перечисления · урок 7 из 8

Optional как перечисление

Опционал выглядит особым типом. У него свой знак ?, своё слово nil и свои способы достать значение: if let и ??. Но за всем этим стоит обычное перечисление с двумя вариантами.

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

Раз опционал — перечисление, его можно разобрать в switch по вариантам:

SWIFT
let parsed = Int("42")
switch parsed {
case .some(let n):
    print("number", n) // number 42
case .none:
    print("no number")
}

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

  1. Int("42") даёт Int?, то есть Optional<Int>. В строке "42" есть число, поэтому в parsed лежит .some(42).
  2. Шаблон .some(let n) подходит под коробку с подарком и достаёт 42 в константу n, как .coffee(let size) доставал размер с чека.
  3. Ветка case .none: нужна для пустой коробки. Здесь она не выполнится, но без неё switch не будет исчерпывающим.
  4. Программа печатает number 42.

Варианты .some и .none

Этот вопрос часто задают на собеседовании: как устроен Optional? Ответ: это перечисление с двумя вариантами:

  • .some — коробка с подарком; само значение лежит в этом варианте как связанное значение;
  • .none — пустая коробка; это и есть nil.

Варианты можно написать и явно:

SWIFT
let empty: Int? = .none
let five: Int? = .some(5)
let sameFive: Int? = 5
print(empty == nil) // true
print(five == sameFive) // true

Давайте пройдём по программе:

  1. В empty лежит .none, и сравнение с nil печатает true: nil — короткая запись .none.
  2. .some(5) явно кладёт число 5 в коробку five.
  3. В sameFive число записано без .some. Тогда Swift сам упаковывает его в .some.
  4. Поэтому five == sameFive печатает true: в обеих коробках лежит .some(5).

В обычном коде .some и .none пишут редко: 5 и nil короче и значат то же самое.

Здесь картинка с меню неточна. Меню написано для одного кафе, а перечисление Optional одно на все типы. В Int? подарок — число, в String? — строка, а Dish(rawValue: 2) даёт Dish?, коробку с блюдом.

if let и if case

Запись if let вы уже знаете. Это короткая форма той же проверки варианта. Сравните её с if case:

SWIFT
if case .some(let n) = parsed {
    print("number", n) // number 42
}
if let n = parsed {
    print("number", n) // number 42
}

Что важно знать об этих двух записях:

  • обе проверяют, лежит ли в parsed вариант .some, и достают из него значение в n;
  • if case работает с любым перечислением, а if let — только с опционалом;
  • к обеим можно добавить else: эта ветка выполнится для .none.

Так же устроен и ??: для .some он отдаёт значение из коробки, а для .none — значение по умолчанию.

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

В switch по опционалу разобрали только коробку с подарком:

SWIFT
switch parsed { // ✗
case .some(let n):
    print("number", n)
}
TEXT
error: switch must be exhaustive

Программа не компилируется. Optional — перечисление, и switch по нему должен разобрать оба варианта, как по любому другому перечислению. Ниже компилятор подсказывает add missing case: '.none', то есть «добавьте ветку для .none». Допишите ветку case .none: для пустой коробки.

Главное

  • Optional — перечисление с вариантами .some со значением внутри и .none; nil — это .none.
  • Значение, которое кладут в опционал, Swift сам упаковывает в .some.
  • switch по опционалу разбирает оба варианта: .some(let n) достаёт значение, а .none ловит пустую коробку.
  • if let n = parsed — короткая запись if case .some(let n) = parsed.