Перейти к содержимому
Теория · Интерфейсы · урок 7 из 10

switch по типу

В прошлом уроке утверждение типа задавало один вопрос — «Вы водитель?», — и этого хватало. Но если новичком может оказаться водитель, повар или бухгалтер, вопросов становится много. Представьте отдел кадров у входа: там смотрят, кто пришёл, и отправляют каждого в свой отдел — водителя в гараж, повара на кухню.

Без такого отдела утверждения с ok выстраиваются в цепочку. Каждый новый тип добавляет ещё одну ветку else if со своей проверкой:

GO
package main

import "fmt"

func main() {
    var v any = "Go"
    if n, ok := v.(int); ok {
        fmt.Println("int", n)
    } else if s, ok := v.(string); ok {
        fmt.Println("string", len(s)) // string 2
    }
}

Давайте разберём проверки по порядку:

  1. Первая спрашивает, не int ли лежит в v, и получает false.
  2. Вторая находит строку и печатает string 2.

Для двух типов цепочка ещё читается, но для пяти она растягивается на экран. switch по типу записывает ту же проверку короче, и каждый новый тип добавляет в нём одну строку case.

Запись v.(type)

switch по типу сравнивает динамический тип значения с типами в ветках case. Это и есть отдел кадров у входа. Заголовок switch x := v.(type) состоит из частей:

  • switch — ключевое слово, как у обычного switch;
  • x := — переменная, которая в каждой ветке получит значение из v;
  • v.(type) — на месте утверждения v.(int) стоит слово type.

Go проверяет ветки сверху вниз и выполняет первую подходящую, как в обычном switch. Функция size возвращает само число для int и длину для строки и среза:

GO
func size(v any) int {
    switch x := v.(type) {
    case int:
        return x
    case string:
        return len(x)
    case []int:
        return len(x)
    case nil:
        return 0
    default:
        return -1
    }
}

Два правила записи:

  • если сами значения не нужны, переменную можно не объявлять: switch v.(type);
  • запись v.(type) допустима только в заголовке switch, а в других местах программа не скомпилируется.

Тип переменной в ветке

Тип x зависит от вида ветки. Разберём три вида из функции size.

Ветка с одним типом

Переменная x в такой ветке получает тип этой ветки. В case int у x тип int, и его можно вернуть как число. В case string у x тип string, и к нему применима len. Кто попал в гараж, тот уже точно водитель, и с ним говорят как с водителем.

Ветка case nil

Эта ветка выполняется, когда в v ничего не положили. У x здесь остаётся тип any.

Вспомните пару из прошлого урока: nil-указатель в ветку case nil не попадёт. Для var p *int вызов size(p) вернёт -1, потому что динамический тип *int в значении интерфейса уже есть.

Ветка default

Ветка default выполняется для всех остальных типов, которых нет среди case. У x здесь тоже остаётся тип any, ведь подойти мог любой тип.

Проверка на значениях

Давайте проверим функцию на разных значениях:

GO
fmt.Println(size(7), size("Go"), size([]int{1, 2, 3})) // 7 2 3
fmt.Println(size(nil), size(2.5))                      // 0 -1

Разберём каждый вызов:

  1. size(7) возвращает число как есть: 7.
  2. size("Go") берёт длину строки: 2.
  3. size([]int{1, 2, 3}) берёт длину среза из трёх элементов: 3.
  4. size(nil) попадает в ветку case nil и даёт 0.
  5. У 2.5 тип float64, и такой ветки нет, поэтому срабатывает default с результатом -1.

Частая ошибка

В одной ветке перечислили два типа и применили к x арифметику:

GO
var v any = 21
switch x := v.(type) {
case int, float64:
    fmt.Println(x * 2) // ✗
}
TEXT
invalid operation: x * 2 (mismatched types any and untyped int)

Когда в case больше одного типа, компилятор не знает, какой из них окажется в x. Поэтому в такой ветке x остаётся типа any, и умножить его нельзя. Здесь картинка с отделом кадров заканчивается: человека за общей дверью можно расспросить ещё раз, а компилятор решает всё при сборке. Разнесите типы по отдельным веткам case int и case float64.

Главное

  • switch x := v.(type) выбирает ветку по динамическому типу v; ветки проверяются сверху вниз.
  • В ветке с одним типом x имеет этот тип, а в case nil, default и ветке с несколькими типами — тип any.
  • case nil срабатывает, только когда в интерфейсе ничего нет; nil-указатель туда не попадает.
  • Запись v.(type) допустима только в заголовке switch.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом