---
title: "Switch: множественный выбор"
description: "Switch в Go: множественный выбор вместо лестницы if, несколько значений в case, switch без выражения, default, fallthrough и type switch. Примеры — в песочнице."
course: "Go с нуля: базовый синтаксис и конструкции"
url: "https://golangmaster.ru/courses/go-basics/lessons/12-switch/"
published: "2026-09-19"
updated: "2026-09-28"
minutes: 18
---

# Switch: множественный выбор

*Курс «Go с нуля: базовый синтаксис и конструкции», урок 12 из 25. ~18 минут. Обновлено 28.09.2026. Источник: https://golangmaster.ru/courses/go-basics/lessons/12-switch/*

Switch в Go — это множественный выбор в чистом виде: одно выражение сравнивается сразу с набором вариантов, и выполняется первая совпавшая ветка. После урока про [if и else](/courses/go-basics/lessons/11-usloviya-if/) у вас уже есть рабочая лестница else if — теперь разберём, когда её стоит переписать на switch, как перечислить несколько значений в одном case, зачем нужен default, почему в Go нет проваливания веток и что такое type switch.

### 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:` — вывод не изменится, а строк станет меньше.

```go
package main

import "fmt"

func main() {
        day := 3
        switch day {
        case 1:
                fmt.Println("понедельник")
        case 2:
                fmt.Println("вторник")
        case 3:
                fmt.Println("среда")
        case 4:
                fmt.Println("четверг")
        case 5:
                fmt.Println("пятница")
        default:
                fmt.Println("такого дня нет:", day)
        }
}
```

### Как 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: печать «воскресенье»; }. Сколько строк напечатает программа?

- Две: обе ветки истинны при n = 7
- ✅ Одну: «воскресенье», после ветки switch сам останавливается
- Ни одной: без break ветка не выполнится
- Одну: «суббота», ветки проверяются снизу вверх

Совпадает только case 7, и после его тела switch завершается сам — неявный break встроен в язык. В C после «воскресенье» выполнение без break утекло бы в следующую ветку; в Go такая ошибка невозможна. Ветки никогда не перебираются снизу вверх — только сверху вниз, до первого совпадения.

### Case с несколькими значениями и switch по строкам

Соседние варианты с одинаковым телом не нужно дублировать: в одном case через запятую перечисляется сколько угодно значений — `case 6, 7:` читается как «если день равен шести или семи». Так же спокойно switch работает со строками, ведь строки в Go сравниваются простым ==, без хитрых функций: команды бота, коды ответов, названия складов. Мы разбирали сравнение строк в уроке про [строки и руны](/courses/go-basics/lessons/06-stroki-i-runes/) — switch просто применяет это сравнение к списку вариантов.

У switch, как и у if, есть инициализирующий оператор: `switch n := len(pin); n {` — переменная объявляется прямо в заголовке, используется в ветках и умирает после конструкции. Приём стоит держать в голове для «временных» значений, которые нужны только ради выбора: подсчитанная длина, вычисленная цена, округлённый балл.

Одно ограничение стоит запомнить сразу: все значения в ветках должны быть сравнимы с выражением switch по типу. Смешать числа и строки в одном switch нельзя — настоящий компилятор откажется собирать программу со словами о несовпадении типов.

### Что выведет код?

```go
package main

import "fmt"

func main() {
        cmd := "stop"
        switch cmd {
        case "start", "go":
                fmt.Println("запускаю")
        case "stop":
                fmt.Println("останавливаю")
        case "status":
                fmt.Println("всё работает")
        }
}
```

**Что выведет программа?**

- запускаю
- ✅ останавливаю
- всё работает
- ничего: ветки на строки не работают

cmd равен "stop", совпадает вторая ветка — печатается «останавливаю». Первая ветка с двумя значениями пропущена: ни "start", ни "go" не равны "stop". Default здесь нет, и он не нужен: список команд закрыт полностью, любое другое значение — забота того, кто допишет ветки.

### Песочница: switch без выражения

Запустите: при speed = 72 печатается «в пределах нормы». Поднимите speed до 95 — теперь первая ветка. Добавьте над ней ещё одну: `case speed > 110:` с предупреждением о протоколе — и проверьте 120: сработает самая верхняя, а до соседних дело не дойдёт. Затем возьмите любую лестницу else if из урока про if и перепишите её на switch — сравните, где список условий читается быстрее.

```go
package main

import "fmt"

func main() {
        speed := 72
        switch {
        case speed > 90:
                fmt.Println("превышение: штраф")
        case speed > 60:
                fmt.Println("в пределах нормы")
        default:
                fmt.Println("тише воды, ниже травы")
        }
}
```

### Switch без выражения или else if: что выбрать

Switch без выражения после ключевого слова — это shorthand для switch true: в ветках пишутся произвольные булевы условия из сравнений, && и || (всё это мы собирали в уроке про [bool и логику](/courses/go-basics/lessons/07-bool-i-logika/)), и выполняется первая истинная. Получается та же лестница else if, только с выровненным левым краем и без череды if/else if.

Важная деталь: в классическом switch с тегом диапазон записать нельзя. `case speed > 90` при числовом speed — попытка сравнить число с bool, компилятор откажет. Поэтому диапазоны живут либо в безвыраженном switch, либо в лестнице if.

Моё правило короткое: одна переменная против списка конкретных значений — switch с тегом; диапазоны и смешанные условия — switch без выражения или if, что из них приятнее глазу. Споры «switch против if» в ревью я обычно пропускаю мимо: конструкции взаимозаменяемы, и тратить на них нитки обсуждения скучно. Но если выбор по значению надо повторять много раз подряд — например, для каждого элемента списка — без циклов уже не обойтись: об этом следующий урок, [цикл for](/courses/go-basics/lessons/13-cikl-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?

- switch speed { case speed > 90: … } — диапазон прямо в case
- ✅ switch { case speed > 90: … } — без выражения, условие в case
- switch speed { case true: … } — ответ через bool-значение
- switch speed { case 60..90: … } — диапазон через две точки

В switch с тегом значения case должны быть того же типа, что и тег: скорость — число, а speed > 90 и true — bool, компилятор скажет о несовпадении типов. Синтаксиса диапазона 60..90 в Go нет вовсе. Безвыраженный switch сравнивает true с булевыми условиями — это единственный из вариантов. Нюанс: песочница несовпадение типов молча пропустит — за проверкой типов там следят не строго, поэтому опечатки ловим глазами и локальным компилятором.

### Fallthrough: провал в следующую ветку

```go
package 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: выбор по типу значения

```go
package 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](/courses/go-advanced/).

### Вставьте ключевые слова switch

Соберите разбор команд бота: при cmd = "exit" ни одна именованная ветка не подходит, и программа должна попасть в запасную — напечатать «неизвестная команда: exit». Четыре пропуска: слово, открывающее выбор, слово для каждой именованной ветки и слово для запасной.

```go
cmd := "exit"
___ cmd {
___ "help":
        fmt.Println("команды: help, start, stop")
___ "start":
        fmt.Println("запускаю")
___:
        fmt.Println("неизвестная команда:", cmd)
}
```

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

Правильные ответы в пропусках: `switch`, `case`, `case`, `default`

### Соберите программу продажи билетов

Соберите switch по зоне зала: зона «B» должна попасть в общий case с зоной «A» и напечатать «партер: 3000 руб.». Строки идут в порядке: шапка программы, переменная, сам switch с ветками и две закрывающие скобки.

Правильный порядок строк:

1. `package main`
2. `import "fmt"`
3. `func main() {`
4. `zone := "B"`
5. `switch zone {`
6. `case "A", "B":`
7. `fmt.Println("партер: 3000 руб.")`
8. `case "C":`
9. `fmt.Println("балкон: 1200 руб.")`
10. `default:`
11. `fmt.Println("зона не найдена")`
12. `}`
13. `}`

### Карточки: switch и множественный выбор

- **Зачем switch, если есть if/else?** — Для множественного выбора: одно выражение сравнивается с набором вариантов сразу, запись короче и читается как меню. Гарантированно выполняется ровно одна ветка — первая совпавшая.
- **Нужен ли break в конце каждого case?** — Нет: в Go ветка завершает switch сама (неявный break). Проваливание в следующую ветку делается только осознанно — оператором fallthrough, который почти всегда лучше заменить запятой в case.
- **Что делает default и где он может стоять?** — Ловит все значения, не подошедшие ни одному case. Пишется в любом месте switch (по конвенции — последним), не больше одного; при совпавшем case не выполняется даже если стоит первым.
- **Как перечислить несколько значений в одном case?** — Через запятую: case 6, 7: или case "start", "go":. Ветка срабатывает, если выражение равно любому из значений; типы значений должны совпадать с типом выражения switch.
- **Как switch работает без выражения?** — switch { case speed > 90: … } эквивалентен switch true: в ветках пишут булевы условия, выполняется первая истинная. Это идиоматичная замена длинных лестниц else if.
- **Что такое type switch?** — switch v := x.(type) { case int: … } — выбор ветки по фактическому типу значения-интерфейса; v внутри ветки получает тип этой ветки. В песочнице не исполняется: доступен type assertion x.(T).

## Коротко: что запомнить

- 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 нельзя, компилятор отклонит программу из-за несовпадения типов.
