1. Иногда полезно извлекать значения из case
Если честно, типичная причина появления монструозных if/else не в том, что люди не знают switch. Причина банальнее: «я хочу не только выбрать ветку, но ещё и использовать конкретное значение». Например: «если число не равно 0 и не равно 1 — напечатай, какое именно оно». И вот тут новичок часто возвращается к if/else, потому что кажется, что switch «слишком строгий».
На самом деле switch в Swift как раз умеет это делать — через pattern matching (сопоставление с паттернами) и binding (привязку значения к имени). Мы будем учиться доставать данные из совпавшего case так, чтобы код оставался читаемым, а не превращался в детектив «кто украл мою переменную и где она объявлена».
Pattern matching и case let
Слово “pattern” звучит так, как будто сейчас начнётся лекция по дизайну интерьера: «паттерны в гостиной», «паттерны в ковре»… Но в программировании это проще: паттерн — это шаблон, с которым мы сравниваем значение. Если значение «подходит под шаблон», ветка case срабатывает.
В Swift case — это не «произвольное условие как в if», а именно сопоставление с паттерном. Паттерном может быть литерал (case 0:), диапазон (case 0..<10:), «любой вариант» (case _:), или «любой вариант, но запомни его в переменную» (case let x:). Синтаксис case let ... и if case let ... — часть официального языка и давно используется как стандартный способ извлечения значений при сопоставлении.
Чтобы мозгу было проще, представьте себе трафарет. Значение — это предмет, который мы пытаемся приложить к трафарету. Подошло — отлично, выполняем код. Не подошло — идём к следующему трафарету (case) ниже.
Небольшая «карта паттернов», которую мы уже фактически используем, но теперь назовём по имени:
| Паттерн | Пример | Идея |
|---|---|---|
| Литерал | |
«Точно равно 0» |
| Несколько литералов | |
«Равно одному из…» |
| Диапазон | |
«Попало в границы» |
| Односторонний диапазон | |
«Меньше нуля» |
| Wildcard | |
«Любое значение, неважно какое» |
| Binding | |
«Любое значение, но сохранить его в x» |
2. case let: подходит всё — и мне нужно значение
В прошлых примерах default часто выглядел так: «всё остальное». Но проблема default в том, что он не даёт удобного имени для значения. То есть технически вы всегда можете обратиться к исходной переменной, но вот если в switch было передано выражение, то хочется прямо в ветке сказать: «а вот это значение — назову его other и выведу». Именно для этого и есть case let.
Самый базовый пример (и он действительно часто встречается в реальном коде):
import Foundation
let n = 42
switch n+1 {
case 0:
print("Zero") // Zero
case 1:
print("One") // (не выведется)
case let other:
print("Other number: \(other)") // Other number: 43
}
Обратите внимание на две детали. Во‑первых, case let other: стоит последним — иначе он «перехватит» всё, и до case 0 мы никогда не дойдём. Во‑вторых, other существует только внутри этого case, это локальная переменная ветки.
Ещё один полезный приём: case let x вместо default, когда вы хотите явно показать намерение «я обработал все варианты, а сюда попадает остальное, и мне важно знать, что именно»:
import Foundation
let command = "abracadabra"
switch command {
case "help":
print("Show help") // (не выведется)
case "exit":
print("Bye!") // (не выведется)
case let unknown:
print("Unknown command: \(unknown)") // Unknown command: abracadabra
}
Да, это очень похоже на default, но психологически читается иначе: не «всё остальное», а «любая строка, и я её сохраняю».
4. case let + where: когда одного совпадения мало
Иногда паттерна недостаточно. Например, вы хотите не просто «поймать любое число», а «поймать число и дополнительно проверить, что оно чётное и положительное». В таких случаях мы комбинируем binding и where.
where в switch — это дополнительное условие: ветка сработает только если совпал паттерн и истинно условие после where. Внутри where мы часто используем связанное имя (например, v), поэтому сначала делаем binding, а потом уточняем.
Пример:
import Foundation
let x = 12
switch x {
case let v where v > 0 && v.isMultiple(of: 2):
print("Positive even: \(v)") // Positive even: 12
case let v where v > 0:
print("Positive odd: \(v)")
case 0:
print("Zero")
default:
print("Negative")
}
Здесь мы используем isMultiple(of:), чтобы не писать v % 2 == 0. Получается более «разговорно»: число кратно двум.
Полезная мысль: case let v без where — это «ловушка для всего». case let v where ... — это «ловушка для всего, но только если выполняется правило».
5. case var: локальная изменяемая копия
Иногда хочется внутри ветки чуть‑чуть «подправить» значение: добавить бонус, применить скидку, увеличить на 5 — и вывести. И тут возникает вопрос: а можно ли внутри case сделать переменную изменяемой? Можно: case var.
Важно понимать: case var создаёт локальную копию значения внутри ветки. Вы меняете её — исходная переменная, по которой делали switch, не меняется. Это не «ссылка» и не «изменение снаружи», а просто удобный способ избежать отдельной строки var temp = value.
Пример:
import Foundation
let points = 10
switch points {
case var x where x > 0:
x += 5
print("Bonus points: \(x)") // Bonus points: 15
default:
print("No points")
}
print(points) // 10
Последний print(points) специально оставлен: он показывает, что исходное значение не изменилось. Это особенно важно для новичков, потому что мозг легко дорисовывает «магическое изменение переменной», которого на самом деле нет.
6. Область видимости и точечные проверки
Область видимости и порядок case
Когда вы впервые начинаете использовать case let, есть две частые эмоции. Первая: «О, как удобно!» Вторая (через пять минут): «Почему я не могу использовать v после switch? Я же его объявил!»
Ответ простой: переменные, объявленные в case, живут только внутри этого блока. Это защитный механизм: иначе switch превращался бы в источник «полуинициализированных» переменных (в одной ветке переменная есть, в другой нет).
Чтобы это почувствовать, вот пример, который новичок часто хочет написать, но он не сработает (и это нормально):
import Foundation
let n = 7
switch n {
case let v:
print("Inside case: \(v)") // Inside case: 7
}
print(v) // ❌ Ошибка: v не видно здесь
И вторая часть — порядок case. Swift проверяет ветки сверху вниз и берёт первую подходящую. Поэтому любые «общие» паттерны (вроде case let x) должны быть ниже более конкретных. Это то же правило, что и с диапазонами: сначала частное, потом общее. Если сделать наоборот, вы сами себе выключите половину логики, а потом будете отлаживать «почему не работает case 0» (спойлер: потому что case let x его уже съел).
if case: одна проверка без полного switch
switch хорош, когда веток несколько. Но иногда вам нужно сделать всего одну проверку вида «попал ли рейтинг в диапазон 1...5», и писать ради этого switch кажется тяжеловато. Вот тут появляется if case.
Форма запоминается как маленькое заклинание: if case ПАТТЕРН = ЗНАЧЕНИЕ. Обратите внимание на порядок: сначала паттерн, потом значение. Это непривычно после if x == 10, но логика такая: «подходит ли значение под этот паттерн».
Пример с диапазоном (очень практично для валидации ввода):
import Foundation
let rating = 4
if case 1...5 = rating {
print("Valid rating") // Valid rating
} else {
print("Invalid rating")
}
И пример с односторонним диапазоном:
import Foundation
let temperature = -3
if case ..<0 = temperature {
print("Below zero") // Below zero
} else {
print("Not below zero")
}
if case — это не «игрушка». Это нормальный инструмент, который помогает убрать маленькие switch и сделать код короче, когда вам не нужно много веток. Технически код выше эквивалентен коду:
import Foundation
let temperature = -3
switch temperature {
case ..<0:
print("Below zero") // Below zero
default:
print("Not below zero")
}
7. Мини‑приложение: ConsoleBuddy
Теперь соберём всё в маленький цельный сценарий, который вы можете расширять дальше по курсу. Мы сделаем консольного помощника ConsoleBuddy: он читает команду ("score", "temp", "exit"), а затем выполняет соответствующую проверку. Идея простая, зато в ней естественно используется case let для неизвестных команд и if case для валидации чисел.
Каркас цикла команд
Цикл нам нужен, чтобы программа не завершалась после одной команды. Мы это уже умеем через while true и break.
import Foundation
while true {
print("Enter command (score/temp/exit): ", terminator: "")
let command = (readLine() ?? "").lowercased()
switch command {
case "exit":
print("Bye!") // Bye!
break
default:
print("Command received: \(command)")
}
}
Этот код пока неправильный логически: break сейчас выйдет только из switch, а не из while. Это как раз повод сделать правильно в следующем шаге.
Правильный выход из цикла и case let для неизвестной команды
Сделаем выход из цикла через break уже в while, а внутри switch будем менять флаг. Это старый, понятный и честный способ, не требующий продвинутых тем.
import Foundation
var isRunning = true
while isRunning {
print("Enter command (score/temp/exit): ", terminator: "")
let command = (readLine() ?? "").lowercased()
switch command {
case "exit":
print("Bye!") // Bye!
isRunning = false
case "score", "temp":
print("Ok, let's work with \(command)")
case let unknown:
print("Unknown command: \(unknown)")
}
}
Вот здесь case let unknown — ровно то, ради чего мы сегодня собрались: мы поймали «всё остальное» и сразу получили строку, которую можно вывести пользователю.
Команда "score": читаем число и проверяем if case
Теперь добавим обработку оценки. Допустим, оценка должна быть от 0 до 100. Мы читаем строку, превращаем в Int и проверяем диапазон через if case.
import Foundation
print("Enter score 0...100: ", terminator: "")
let score = Int(readLine() ?? "") ?? -1
if case 0...100 = score {
print("Score is valid: \(score)") // например: Score is valid: 97
} else {
print("Invalid score") // если ввели ерунду
}
Обратите внимание на дефолт -1: он специально выбран так, чтобы точно не пройти проверку. Да, это немного «хитро», но на текущем уровне это простой и рабочий приём.
Классификация score через switch и case let
Теперь то же самое можно красиво классифицировать через switch, и при этом иногда нам нужно вывести само значение.
import Foundation
let score = 97
switch score {
case 100:
print("Perfect") // (не выведется)
case 90..<100:
print("Great") // Great
case 60..<90:
print("OK")
case 0..<60:
print("Fail")
case let s:
print("Invalid score: \(s)")
}
Фишка тут в том, что вместо тупого default: print("Invalid") мы сообщаем пользователю, что именно пришло (например, -1 или 120). Это мелочь, но это уже «зачатки отладки» прямо в UX.
Команда "temp": используем case let v where ...
Температуру классифицируем с помощью where, чтобы показать «правило» без вложенного if.
import Foundation
let t = -5
switch t {
case let v where v < 0:
print("Below zero: \(v)") // Below zero: -5
case 0:
print("Exactly zero")
case let v where v <= 25:
print("Comfortable: \(v)")
default:
print("Hot")
}
Здесь два case let v where ... — и это нормально: мы просто используем одинаковое имя v в разных ветках, потому что область видимости у них разная.
8. Типичные ошибки при использовании case let и if case
Ошибка №1: поставить case let x слишком рано и «перехватить всё».
Такой case подходит под любое значение, поэтому если он стоит выше конкретных случаев (case 0, case 1...5, case 90..<100), эти случаи становятся недостижимыми. Лечится это дисциплиной порядка: сначала узкие паттерны, потом широкие.
Ошибка №2: ожидать, что переменная из case let доступна после switch.
Переменная, объявленная в case, живёт только внутри соответствующей ветки. Это не «глобальная переменная, объявленная внутри switch», а локальная переменная блока. Если вам нужно значение снаружи, значит, вы проектируете логику так, что нужно другое решение (но сейчас достаточно помнить: область видимости ограничена).
Ошибка №3: думать, что case var меняет исходную переменную.
case var x даёт изменяемую копию, а не доступ к исходному значению «по ссылке». Если вы увеличили x внутри ветки, это не значит, что переменная, по которой делали switch, тоже изменилась. Проверяйте это отдельным print — и мозг быстро перестанет «додумывать магию».
Ошибка №4: перепутать порядок в if case и писать «как в математике».
Правильная форма: if case ПАТТЕРН = ЗНАЧЕНИЕ. Новички часто пытаются написать наоборот, потому что «так привычнее». Тут поможет только привычка: читайте это как фразу «если случай (паттерн) подходит значению».
Ошибка №5: пытаться превратить switch в if, записывая «условия» вместо паттернов.
Конструкция вида case x > 0: не работает, потому что это не паттерн. Если вам нужна дополнительная логика, используйте where, а если логики слишком много — иногда честнее вернуться к if (но осознанно, а не потому что «switch не умеет»).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ