· 8 мин 👁 1.8k Начинающий

Язык Go - switch

Switch в Go без fall-through по умолчанию, с условиями вместо значений, метками для выхода из цикла и type switch для работы с интерфейсами.

switchtype switchинтерфейсыметки
Содержание

Switch в Go выглядит знакомо — но работает иначе, чем в C, Java или JavaScript. Без автоматического провала в следующий case, с условиями вместо значений и с отдельной формой для типов. Давайте по порядку.

Базовая форма и отсутствие fall-through

В C-подобных языках если не написать break — выполнение «проваливается» в следующий case. В Go всё наоборот: каждый case завершается сам, break писать не нужно. Это убирает целый класс ошибок.

func dayType(day string) string {
    switch day {
    case "Saturday", "Sunday": // несколько значений через запятую — одно условие
        return "выходной"
    case "Monday":
        return "понедельник, тяжёлый день"
    default:
        return "рабочий день"
    }
}

Несколько значений в одном case — через запятую. Без дублирования кода, без fall-through.

Если fall-through всё-таки нужен — есть явное ключевое слово fallthrough. Встречается редко и обычно сигнализирует, что стоит пересмотреть структуру кода.

Switch без выражения — замена длинным if-else

Если убрать выражение после switch, Go считает, что переключаемся на true. Это позволяет писать условия прямо в case:

func unhex(c byte) byte {
    switch { // нет выражения — switch on true
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

Это прямая замена цепочке if-else if-else if. Читается чище, особенно когда условий больше трёх. В Go такой стиль считается идиоматичным.

Ещё пример — функция, которая решает, надо ли экранировать символ в URL:

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

break внутри switch — осторожно

break завершает switch, а не внешний цикл. Это неочевидно и может сломать логику.

Представьте: вы парсите байты в цикле, внутри — switch. Нужно прервать именно цикл при ошибке:

Loop: // метка на цикле
    for n := 0; n < len(src); n += size {
        switch {
        case src[n] < sizeOne:
            if validateOnly {
                break // выходим из switch, цикл продолжается
            }
            size = 1
            update(src[n])

        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                err = errShortInput
                break Loop // выходим из цикла по метке
            }
            if validateOnly {
                break // выходим только из switch
            }
            size = 2
            update(src[n] + src[n+1]<<shift)
        }
    }

break без метки — выход из switch. break Loop — выход из цикла с меткой Loop. Это разные вещи. Перепутать легко, компилятор не предупредит — оба варианта синтаксически корректны.

continue с меткой тоже работает, но только для циклов — к switch он не применим.

Практический пример: сравнение слайсов

Посмотрим, как switch без выражения используется в реальной задаче. Функция лексикографического сравнения двух байтовых слайсов:

// Compare возвращает:
//  0, если a == b
// -1, если a < b
// +1, если a > b
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch { // сравниваем побайтово
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        // если равны — идём дальше
        }
    }
    // все общие байты совпали — смотрим на длину
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

Два отдельных switch вместо вложенных if — и логика читается линейно сверху вниз.

Type switch: узнать тип из интерфейса

Отдельная форма switch — для работы с интерфейсами. Когда переменная имеет тип interface{} (или любой другой интерфейс), её конкретный тип неизвестен на этапе компиляции. Type switch позволяет его определить:

var t interface{}
t = functionOfSomeType()

switch t := t.(type) { // t.(type) — специальный синтаксис только внутри switch
default:
    fmt.Printf("неизвестный тип %T\n", t)     // %T — печатает тип переменной
case bool:
    fmt.Printf("булево: %t\n", t)             // здесь t имеет тип bool
case int:
    fmt.Printf("целое: %d\n", t)              // здесь t имеет тип int
case *bool:
    fmt.Printf("указатель на bool: %t\n", *t) // здесь t имеет тип *bool
case *int:
    fmt.Printf("указатель на int: %d\n", *t)  // здесь t имеет тип *int
}

Обратите внимание на switch t := t.(type) — здесь переменная t переобъявляется. Снаружи t — это interface{}. Внутри каждого case — уже конкретный тип. Одно имя, разные типы в разных ветках. Выглядит странно, но это идиоматичный Go.

Где это реально нужно

Type switch встречается при работе с JSON, когда структура заранее неизвестна:

func describe(v interface{}) string {
    switch val := v.(type) {
    case nil:
        return "null"
    case bool:
        if val {
            return "true"
        }
        return "false"
    case float64:
        return fmt.Sprintf("число: %g", val) // json.Unmarshal числа → float64
    case string:
        return fmt.Sprintf("строка: %q", val)
    case []interface{}:
        return fmt.Sprintf("массив из %d элементов", len(val))
    case map[string]interface{}:
        return fmt.Sprintf("объект с %d полями", len(val))
    default:
        return fmt.Sprintf("что-то неожиданное: %T", val)
    }
}

Ловушка с несколькими типами в одном case

Если перечислить несколько типов через запятую — переменная внутри case остаётся interface{}, а не конкретным типом:

switch v := x.(type) {
case int, int64: // оба целочисленных
    fmt.Println(v) // v здесь interface{}, не int и не int64
    // нельзя написать v + 1 — компилятор не разрешит
}

Это логично: Go не знает, какой именно тип использовать для v. Если нужно работать со значением — разделяйте на отдельные case.

Итоги

  • Fall-through в Go нет по умолчанию — каждый case завершается сам
  • Несколько значений в одном case — через запятую
  • Switch без выражения — это switch on true, замена цепочке if-else if
  • break внутри switch прерывает switch, не цикл — для цикла нужна метка
  • t.(type) работает только внутри switch, не как отдельное выражение
  • В type switch с несколькими типами в одном case переменная остаётся interface{}