switch по типу
В прошлом уроке утверждение типа задавало один вопрос — «Вы водитель?», — и этого хватало. Но если новичком может оказаться водитель, повар или бухгалтер, вопросов становится много. Представьте отдел кадров у входа: там смотрят, кто пришёл, и отправляют каждого в свой отдел — водителя в гараж, повара на кухню.
Без такого отдела утверждения с ok выстраиваются в цепочку. Каждый новый тип добавляет ещё одну ветку else if со своей проверкой:
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
}
}Давайте разберём проверки по порядку:
- Первая спрашивает, не
intли лежит вv, и получаетfalse. - Вторая находит строку и печатает
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 и длину для строки и среза:
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, ведь подойти мог любой тип.
Проверка на значениях
Давайте проверим функцию на разных значениях:
fmt.Println(size(7), size("Go"), size([]int{1, 2, 3})) // 7 2 3
fmt.Println(size(nil), size(2.5)) // 0 -1Разберём каждый вызов:
size(7)возвращает число как есть:7.size("Go")берёт длину строки:2.size([]int{1, 2, 3})берёт длину среза из трёх элементов:3.size(nil)попадает в веткуcase nilи даёт0.- У
2.5типfloat64, и такой ветки нет, поэтому срабатываетdefaultс результатом-1.
Частая ошибка
В одной ветке перечислили два типа и применили к x арифметику:
var v any = 21
switch x := v.(type) {
case int, float64:
fmt.Println(x * 2) // ✗
}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 и подпишись на канал — откроются все уроки.