1. Зачем разбирать Optional через switch
Если вы привыкли распаковывать optional через if let, то вопрос логичный: «Зачем мне switch, если и так всё работает?». Здесь идея не в том, что switch «лучше», а в том, что он иногда выразительнее. if let отвечает на вопрос «значение есть или нет», а switch позволяет в одном месте описать и отсутствие значения, и отдельные «особые значения» внутри .some.
Ещё одна причина — читабельность, когда веток становится больше двух. Как только у вас появляются правила уровня «если значение есть и оно равно нулю — одно поведение; если больше нуля — другое; если nil — третье», switch обычно получается проще и честнее. К тому же switch заставляет нас помнить про nil: исчерпывающий разбор — это когда вы не «забыли обработать отсутствие», а явно это зафиксировали.
Optional — это enum: .some и .none
Чтобы switch по optional не выглядел как шаманство, ещё раз проговорим модель. У Optional два состояния:
- .some(Wrapped) — значение есть,
- .none — значения нет (и этот случай мы обычно пишем как nil).
Когда вы пишете let x: Int? = 10, в голове можно держать эквивалент: «в x лежит .some(10)». А когда пишете nil, это «в x лежит .none».
Мини-проверка на уровне кода (не для «практики», а чтобы глазами увидеть модель):
import Foundation
let a: Int? = 10
let b: Optional<Int> = .some(10)
let c: Int? = nil
let d: Optional<Int> = .none
print(a as Any, b as Any, c as Any, d as Any) // Optional(10) Optional(10) nil nil
Обратите внимание на as Any: без него print иногда подсказывает тип не так наглядно, а нам сейчас важнее увидеть «optional-форму».
2. switch по Optional: основные паттерны
Базовый разбор .some и .none
Когда мы переключаемся по optional, самый прямой (и самый «учебник-стайл») вариант — явно разбирать .some и .none. Это выглядит многословно, зато идеально подходит, когда вы хотите подчеркнуть: «Да, я помню про nil и обработал его».
import Foundation
let maybeYear: Int? = 2026
switch maybeYear {
case .some(let year):
print("Год из ввода: \(year)")
case .none:
print("Год не указан")
}
Здесь year внутри .some — уже не optional, а обычный Int. Это важная мысль: switch — такой же способ «перейти от T? к T», как if let, просто форма другая.
Если хочется чуть больше самоиронии, то можно думать так: switch заставляет вас признать, что во вселенной существует nil, даже если вы делаете вид, что не существует.
Ветвление по значениям внутри .some
В if let вы обычно сначала распаковали, а потом пошли во второй if проверять число. В switch можно описать правила сразу. Это особенно приятно, когда есть маленький набор «особых случаев».
import Foundation
let maybeScore: Int? = 0
switch maybeScore {
case .some(0):
print("Счёт ровно ноль")
case .some(let s) where s > 0:
print("Плюсовой счёт: \(s)")
case .some:
print("Отрицательный счёт")
case .none:
print("Счёт отсутствует")
}
Здесь case .some: без let означает: «значение есть, но мне сейчас не важно какое». А в ветке case .some(let s) where s > 0: мы показываем, что switch умеет сочетать извлечение значения и условие в стиле «проверили и сразу назвали переменную».
case nil и case 0: компактная запись и её смысл
Когда люди впервые видят switch по optional, их иногда удивляет, что можно писать case 0: без .some(0). Это выглядит так, будто switch «сам догадался». И он действительно догадался, потому что у Swift есть специальные паттерны сопоставления optional, которые делают запись компактнее.
Пример:
import Foundation
let maybeNumber: Int? = 1
switch maybeNumber {
case 0:
print("Ноль")
case 1:
print("Один")
case nil:
print("Нет значения")
default:
print("Что-то другое")
}
Здесь case 0 и case 1 фактически означают «случай .some(0) и .some(1)», а case nil — это .none.
Почему это важно: если вы не понимаете, что case 0 — это на самом деле .some(0), вы можете начать писать код, который случайно становится нечитаемым (особенно когда тип не очевиден).
Небольшая табличка-«шпаргалка» для мозга:
| Что пишем в switch | Что это значит по смыслу |
|---|---|
|
|
|
|
|
Осторожно: x будет optional, а не «развёрнутое значение» |
|
«Развёрнутое значение» x (уже не optional) |
Optional pattern ?: коротко «значение есть»
Существует очень удобный паттерн: case let value?. Он читается так: «если значение не nil, извлеки его в value». Это прямой аналог case .some(let value), но компактнее.
import Foundation
let maybeTitle: String? = "Swift 6.2 для людей"
switch maybeTitle {
case let title?:
print("Заголовок: \(title)")
case nil:
print("Заголовка нет")
}
Обратите внимание: title тут типа String, а не String?. Именно в этом смысл ?-паттерна: он не просто «проверяет», он «проверяет и разворачивает».
Подводный камень: case let x не распаковывает optional
Очень типичная ловушка новичка: написать case let x: и ожидать, что x — уже «развёрнутое» значение. Но если вы не использовали ?-паттерн и не написали .some(let ...), то переменная останется optional.
Посмотрим глазами:
import Foundation
let maybeName: String? = "Ana"
switch maybeName {
case let x:
print(x) // Optional("Ana")
}
Да, это компилируется. Да, оно «работает». Но в x лежит String?, то есть вы ничего не распаковали — просто переименовали переменную. Это иногда полезно (например, если вы специально хотите передать optional дальше), но если ваша цель — получить String, нужно писать либо case .some(let x), либо case let x?.
Сравните вариант, который действительно разворачивает значение:
import Foundation
let maybeName: String? = "Ana"
switch maybeName {
case let name?:
print(name) // Ana
case nil:
print("Нет имени")
}
3. if case .some: точечные проверки
if case .some(let x): когда else не нужен
Иногда вам не нужен полный switch, потому что сценарий всего один: «Если значение есть — сделай что-то, если нет — просто ничего не делай». Да, это можно сделать через if let. Но if case полезен, когда вы хотите проверить не просто «есть/нет», а конкретный паттерн.
Начнём с базовой формы:
import Foundation
let maybeToken: String? = "abc123"
if case .some(let token) = maybeToken {
print("Токен: \(token)")
}
Это читается довольно буквально: «если optional — это .some, достань значение в token». Если optional равен nil, тело if просто не выполнится.
Сравнение по смыслу:
- if let token = maybeToken { ... } — «распакуй, если есть».
- if case .some(let token) = maybeToken { ... } — «сопоставь с образцом .some(...)».
if case как «умный фильтр»: конкретные значения и условия
Теперь тот же приём, но с «особыми значениями». Это как switch, только для одной ветки.
import Foundation
let maybeCount: Int? = 0
if case .some(0) = maybeCount {
print("Счётчик на нуле") // Счётчик на нуле
}
Или чуть интереснее — проверяем «значение есть и оно подходит»:
import Foundation
let maybeAge: Int? = 21
if case .some(let age) = maybeAge, age >= 18 {
print("Доступ разрешён: \(age)")
}
Здесь мы не заставляем себя писать полный switch, потому что другие ветки нам не важны: если nil или возраст меньше 18 — просто ничего не делаем (или делаем отдельным else, если нужно).
4. Примеры из кода: книга и словарь
Печать книги без «лесенки if»
Чтобы примеры были похожи на реальный код, давайте продолжим линию нашего учебного CLI-приложения LibraryCLI. На этом этапе курса у нас ещё нет продвинутого парсинга команд и слоёв архитектуры (это будет позже), но у нас уже могут быть простые модели и хранение в коллекциях.
Представим простую модель:
import Foundation
struct Book {
let id: Int
let title: String
let subtitle: String?
}
Теперь печать информации о книге. Частая задача: «subtitle может отсутствовать, но если есть — покажем красиво». Можно написать if let, а можно — выразительно через switch, особенно если есть несколько вариантов форматирования.
import Foundation
func printBook(_ book: Book) {
switch book.subtitle {
case let subtitle?:
print("#\(book.id): \(book.title) — \(subtitle)")
case nil:
print("#\(book.id): \(book.title)")
}
}
Здесь switch по book.subtitle аккуратно отделяет два сценария: «подзаголовок есть» и «подзаголовка нет». Код линейный, без вложенности, и не требует помнить, что subtitle — optional: ветки это объясняют сами.
А теперь пример «точечного» случая с if case: допустим, мы хотим добавлять пометку, если subtitle пустой строкой (да, это отдельный смысл: nil — «нет значения», а "" — «значение есть, но оно пустое»).
import Foundation
func warnIfEmptySubtitle(_ book: Book) {
if case .some(let subtitle) = book.subtitle, subtitle.isEmpty {
print("Книга #\(book.id): subtitle задан, но пустой")
}
}
switch по результату поиска в словаре
В реальности optional чаще всего появляется не потому, что мы его «захотели», а потому что API его возвращает. Классика: Dictionary возвращает Optional, потому что ключ может отсутствовать.
import Foundation
let titlesByID: [Int: String] = [1: "Clean Code", 2: "Swift Basics"]
let maybeTitle = titlesByID[3] // String?
switch maybeTitle {
case let title?:
print("Нашли: \(title)")
case nil:
print("Книги с таким ID нет")
}
Снова видно: switch делает ветвление по наличию/отсутствию значения абсолютно явным, и это часто читается лучше, чем «цепочка из ??», когда вам нужно именно разное поведение, а не дефолт.
5. Типичные ошибки
Ошибка №1: забыть обработать nil, потому что «в моих данных его не будет».
Это один из самых частых самообманов. В реальном коде optional появляется именно там, где nil возможен (ввод пользователя, поиск в словаре, парсинг строки). Если switch не исчерпывающий, компилятор обычно не даст собрать код, и это хорошо. Но иногда люди пытаются «откупиться» default-веткой и фактически прячут nil под ковёр. Лучше либо явно писать case nil:, либо делать default так, чтобы было понятно, что именно туда попадает.
Ошибка №2: написать case let x и думать, что x — уже развёрнутое значение.
В таком виде вы просто переносите optional в новую переменную, и дальше удивляетесь, почему print показывает Optional(...). Если вы хотите получить именно T, используйте case .some(let x) или case let x?. Визуально разница маленькая, но по смыслу это два разных мира: «я распаковал» и «я не распаковал».
Ошибка №3: превращать switch в «просто потому что могу».
Иногда switch пишут там, где достаточно if let, и код становится длиннее без пользы. Если у вас две ветки («есть/нет») и внутри обеих по 1–2 строки, if let обычно проще. switch раскрывается, когда появляются особые значения (0, пустая строка), условия (where), или когда вы хотите подчеркнуть исчерпывающую логику.
Ошибка №4: использовать if case .some и забывать, что else всё ещё может быть нужен.
if case часто применяют как «точечный фильтр» без else, но иногда nil — это не «ничего страшного», а полноценная ветка логики, где нужно вывести сообщение или вернуть значение. Если nil важен — лучше switch (или if let ... else), чтобы читатель кода не искал глазами «а что будет при nil?».
Ошибка №5: путать nil и «пустое значение» (0, "", []).
nil означает отсутствие значения, а пустая строка или ноль — значение есть, просто оно такое. switch как раз хорош тем, что можно явно разделить эти смыслы: case nil: отдельно, case .some(""): отдельно, и код перестаёт быть логической кашей. В противном случае рождаются баги вроде «мы считали, что подзаголовка нет, а он оказался пустой строкой, и форматирование поехало».
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ