Switch в go: когда if становится лестницей#
Представьте бота, который принимает команды: "start", "stop", "status", "help". Лестницей else if это выглядит как пять проверок одной и той же переменной подряд — пять строк, в которых cmd == повторяется, как заевшая пластинка. Когда одна переменная или одно выражение сравнивается со списком значений, Go предлагает короткую запись — оператор switch, множественный выбор:
switch cmd { case "start": … case "stop": … default: … } — слева слово switch и выражение, дальше фигурные скобки, внутри ветки case со значением и двоеточием, а default ловит всё, что не подошло. Круглых скобок вокруг выражения нет, как и у if. Значения case не обязаны быть константами — подойдёт любое выражение того же типа, что и само выражение switch.
Работает это так же, как лестница else if из прошлого урока: варианты перебираются сверху вниз, и первая же совпавшая ветка выполняется. Разница в форме и в жёсткой гарантии: в switch выполнится ровно одна ветка, без исключений, и при взгляде на код список вариантов виден сразу — как меню.
Песочница: расписание по дням недели
Запустите: при day = 3 печатается «среда», остальные ветки молча пропускаются. Поставьте day = 6 — сработает default, потому что ветки на 6 и 7 ещё не написаны. Допишите их двумя отдельными case со строкой «выходной», запустите, а затем объедините в одну ветку `case 6, 7:` — вывод не изменится, а строк станет меньше.
Запустите код, чтобы увидеть вывод.
Как switch выбирает ветку: неявный break и default#
Выражение switch вычисляется один раз, после чего значения case перебираются сверху вниз. Первая ветка, чьё значение совпало, выполняется — и на этом switch заканчивается: нижние ветки не трогаются, даже если их значения тоже совпадают. В конце тела каждой ветки как будто стоит невидимый break — за это свойство отвечают слова «неявный break». В C или Java без явного break выполнение проваливалось бы в следующую ветку, и забытый break годами входил в списки самых дорогих багов; в Go проваливание убрали по умолчанию, а оставили как отдельный оператор — о нём ниже.
Ветка default — это финальный else: она ловит все значения, не подошедшие ни одному case. Писать её можно в любом месте switch, хоть первой, но выполнится она всё равно последней по очереди решения — только если ни один case не совпал. Не больше одного default на switch, и по конвенции его ставят последним, чтобы он читался как подвал списка. А если default нет и ни один вариант не совпал, switch просто пропускается: программа едет дальше, ошибки нет. Для ввода пользователя default почти всегда нужен — «неизвестная команда» это нормальная ситуация, а не сбой.
Проверьте себя: сколько строк выведет switch
n := 7, и перед нами switch n { case 6: печать «суббота»; case 7: печать «воскресенье»; }. Сколько строк напечатает программа?
Case с несколькими значениями и switch по строкам#
Соседние варианты с одинаковым телом не нужно дублировать: в одном case через запятую перечисляется сколько угодно значений — case 6, 7: читается как «если день равен шести или семи». Так же спокойно switch работает со строками, ведь строки в Go сравниваются простым ==, без хитрых функций: команды бота, коды ответов, названия складов. Мы разбирали сравнение строк в уроке про строки и руны — switch просто применяет это сравнение к списку вариантов.
У switch, как и у if, есть инициализирующий оператор: switch n := len(pin); n { — переменная объявляется прямо в заголовке, используется в ветках и умирает после конструкции. Приём стоит держать в голове для «временных» значений, которые нужны только ради выбора: подсчитанная длина, вычисленная цена, округлённый балл.
Одно ограничение стоит запомнить сразу: все значения в ветках должны быть сравнимы с выражением switch по типу. Смешать числа и строки в одном switch нельзя — настоящий компилятор откажется собирать программу со словами о несовпадении типов.
Что выведет код?
Прочитайте код и выберите, что он выведет:
package main
import "fmt"
func main() {
cmd := "stop"
switch cmd {
case "start", "go":
fmt.Println("запускаю")
case "stop":
fmt.Println("останавливаю")
case "status":
fmt.Println("всё работает")
}
}Песочница: switch без выражения
Запустите: при speed = 72 печатается «в пределах нормы». Поднимите speed до 95 — теперь первая ветка. Добавьте над ней ещё одну: `case speed > 110:` с предупреждением о протоколе — и проверьте 120: сработает самая верхняя, а до соседних дело не дойдёт. Затем возьмите любую лестницу else if из урока про if и перепишите её на switch — сравните, где список условий читается быстрее.
Запустите код, чтобы увидеть вывод.
Switch без выражения или else if: что выбрать#
Switch без выражения после ключевого слова — это shorthand для switch true: в ветках пишутся произвольные булевы условия из сравнений, && и || (всё это мы собирали в уроке про bool и логику), и выполняется первая истинная. Получается та же лестница else if, только с выровненным левым краем и без череды if/else if.
Важная деталь: в классическом switch с тегом диапазон записать нельзя. case speed > 90 при числовом speed — попытка сравнить число с bool, компилятор откажет. Поэтому диапазоны живут либо в безвыраженном switch, либо в лестнице if.
Моё правило короткое: одна переменная против списка конкретных значений — switch с тегом; диапазоны и смешанные условия — switch без выражения или if, что из них приятнее глазу. Споры «switch против if» в ревью я обычно пропускаю мимо: конструкции взаимозаменяемы, и тратить на них нитки обсуждения скучно. Но если выбор по значению надо повторять много раз подряд — например, для каждого элемента списка — без циклов уже не обойтись: об этом следующий урок, цикл for.
Шпаргалка: if или switch#
| Ситуация | Чем пользоваться | Как выглядит |
|---|---|---|
| Одна переменная против набора значений | switch с выражением | switch day { case 6, 7: … } |
| Диапазоны, комбинированные условия | switch без выражения или else if | switch { case speed > 90: … } |
| Одно условие, два исхода | if / else | if err != nil { … } |
| Вспомогательная переменная для выбора | init-оператор в заголовке | switch n := len(s); n { … } |
| Осознанный провал в следующую ветку | fallthrough (редко) | case 1: … fallthrough |
Проверьте себя: типы в case
speed := 72. Какой из этих switch соберёт настоящий компилятор Go?
Fallthrough: провал в следующую ветку
GOpackage main
import "fmt"
func main() {
n := 1
switch n {
case 1:
fmt.Println("один")
fallthrough
case 2:
fmt.Println("два")
case 3:
fmt.Println("три")
}
}Пример с оговоркой по движкам: основной движок песочницы — настоящий Go — fallthrough исполняет, и программа напечатает «один» и «два»: fallthrough переносит управление в тело следующей ветки, не проверяя её условие. Кнопки запуска у блока нет: резервный упрощённый движок отвечает «fallthrough не поддерживается в песочнице», а блок обязан корректно работать в любом режиме. Ставить fallthrough можно только последним оператором в теле ветки, а в последней ветке switch он запрещён вовсе. В код-ревью я отношусь к fallthrough подозрительно: почти всегда он означает, что автор хотел case 1, 2: и не заметил, либо действие веток стоит вынести в общую строку ниже. Проваливание веток — источник нашумевших багов в C-подобных языках, и Go отказался от него не случайно.
Switch — выбор одной дороги. Даже если значения в двух разных case оба совпадают с выражением, выполнится только та ветка, что стоит выше; до второй дело не дойдёт. Пришедшие из лестницы if, где две независимые проверки могли сработать обе, иногда «накапливают» действия в соседних ветках и недосчитываются строк в выводе. Нужны независимые проверки — пишите независимые if: switch здесь не подменит.
Type switch: выбор по типу значения
GOpackage main
import "fmt"
func describe(x any) string {
switch v := x.(type) {
case int:
return fmt.Sprintf("целое число: %d", v)
case string:
return fmt.Sprintf("строка из %d символов", len(v))
default:
return fmt.Sprintf("не знаю, что за тип %T", v)
}
}
func main() {
fmt.Println(describe(42))
fmt.Println(describe("secret"))
fmt.Println(describe(2.5))
}Тоже пример с оговоркой по движкам: type switch исполняет настоящий Go и основной движок песочницы, а резервный упрощённый отвечает «type switch (x.(type)) не поддерживается в песочнице; используйте type assertion x.(T)» — поэтому блок оставлен без кнопки запуска. Идея такая: x объявлен как any — значение любого типа; выражение x.(type) разрешено только в switch и выбирает ветку по фактическому типу значения. Переменная v, объявленная через := в заголовке, внутри каждой ветки получает тип этой ветки: в int-ветке её можно печатать через %d, в string-ветке — мерить len. В default v остаётся any. Настоящий Go и основной движок песочницы выведут три строки: «целое число: 42», «строка из 6 символов», «не знаю, что за тип float64». Отдельные проверки типов работают в любом движке — type assertion x.(T) с ok-формой, а системно про интерфейсы поговорим в курсе go-advanced.
Вставьте ключевые слова switch
Соберите разбор команд бота: при cmd = "exit" ни одна именованная ветка не подходит, и программа должна попасть в запасную — напечатать «неизвестная команда: exit». Четыре пропуска: слово, открывающее выбор, слово для каждой именованной ветки и слово для запасной.
Соберите программу продажи билетов
Соберите switch по зоне зала: зона «B» должна попасть в общий case с зоной «A» и напечатать «партер: 3000 руб.». Строки идут в порядке: шапка программы, переменная, сам switch с ветками и две закрывающие скобки.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: switch и множественный выбор
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Switch — множественный выбор: выражение вычисляется один раз, выполняется первая совпавшая ветка, остальные пропускаются даже при совпадении.
- Break в конце case не нужен — он встроен в язык; осознанный провал вниз пишут через fallthrough, и почти всегда вместо него читается запятая в case.
- Несколько значений перечисляются через запятую: case 6, 7: — срабатывает при любом из них.
- default ловит всё остальное, может стоять в любом месте (по конвенции — последним) и не выполняется, когда совпал case.
- switch без выражения — это switch true: в ветках булевы условия, побеждает первая истинная проверка.
- В case с тегом не пишут диапазоны: case speed > 90 при числовом speed — ошибка несовпадения типов; для диапазонов — безвыраженный switch или if.
- Type switch switch v := x.(type) выбирает ветку по фактическому типу значения; в песочнице не исполняется, type assertion — доступен.
Домашнее задание
Возьмите кассу кинотеатра из домашки прошлого урока и перепишите её на switch без выражения: age := 15, price := 250; до 12 включительно или от 65 — «цена: 125», от 13 до 18 — «цена: 200», остальным — «цена: 250». Проверьте три возраста: 15, 70 и 30. Затем добавьте классический switch по строке film := "night": case "day", "kids" — «билет 200 руб.», case "night" — «билет 350 руб.», default — «сеанс не найден». Проверьте все три значения и одно выдуманное.
Личные заметкиПусто
Вопросы и ответы по уроку
Чем switch в Go отличается от switch в C или Java?
В C и Java без break выполнение проваливается в следующую ветку, и забытый break — классический источник багов. В Go проваливания по умолчанию нет: ветка завершает switch сама, а осознанный провал пишут словом fallthrough. Ещё в Go можно перечислять несколько значений в одном case и вообще опускать выражение после switch.
Обязателен ли default в switch?
Нет. Если ни один case не совпал и default нет, switch просто пропускается без ошибки. Но там, где приходит ввод пользователя, default почти всегда стоит: «неизвестная команда» или «такого дня нет» — это ожидаемая ситуация, которую надо обработать, а не игнорировать.
Можно ли использовать в case переменные, а не константы?
Да: значения case — любые выражения совместимого типа, константность не требуется. Главное, чтобы все значения были сравнимы с выражением switch: смешать int и string в одном switch нельзя, компилятор отклонит программу из-за несовпадения типов.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Продвинутые возможности Go: от замыканий до рефлексии
Продвинутый курс Go: замыкания, defer, горутины, каналы, контекст, интерфейсы, дженерики, рефлексия, тесты и JSON. 25 уроков с практикой в браузере.
- Писать и читать конкурентный код: go-функции, каналы, select
- Управлять жизненным циклом операций через context
- Проектировать API на интерфейсах
- Использовать дженерики без злоупотребления