Какой статический тип имеет переменная в ветви type switch, если совпал конкретный тип?
В ветви type switch переменная получает статический тип, указанный в совпавшем case. Поэтому для конкретного типа внутри ветви доступны его поля и методы без дополнительного приведения; в ветви с интерфейсным типом переменная имеет именно этот интерфейсный тип.
Type switch является частью модели интерфейсов Go и предназначен для безопасного выбора поведения по динамическому типу значения. Он решает задачу, которая иначе потребовала бы последовательности проверок типа и отдельных приведений.
Подход сохраняет статическую проверку типов: компилятор знает тип переменной внутри каждой ветви и может проверить обращение к её методам и полям.
Интерфейс хранит динамический тип и значение, но обращение к нему обычно ограничено методами статического типа интерфейса. Если обработчику нужно различать несколько конкретных реализаций, важно понимать, какой тип получает переменная внутри каждой ветви.
Ошибка в этом понимании приводит к лишним приведениями типов, недоступным полям или попытке использовать методы, которых нет у интерфейсного статического типа.
Если совпал case с конкретным типом, переменная внутри ветви имеет этот конкретный статический тип. Если совпал case с интерфейсом, переменная имеет статический тип указанного интерфейса, даже если фактическое значение представлено конкретной структурой.
В ветви с несколькими типами переменная сохраняет статический тип исходного интерфейсного выражения, поскольку у перечисленных типов может не быть общего набора полей или методов, известного компилятору. В default действует тот же принцип: переменная имеет тип выражения, проверяемого в type switch.
В первой ветви v имеет тип Login, поэтому доступно поле User. Во второй ветви v имеет тип Event, поэтому доступны только методы этого интерфейса.
Важное следствие: порядок ветвей имеет значение, если динамический тип удовлетворяет нескольким интерфейсным case. Будет выбрана первая подходящая ветвь, поэтому более специфичные случаи обычно размещают раньше общих.
Сервис принимает интерфейс Event и должен отдельно обрабатывать вход пользователя, выход пользователя и неизвестные события. Можно использовать reflection, последовательные проверки типа или type switch.
Reflection гибок, но усложняет код и переносит ошибки на время выполнения. Последовательные проверки требуют повторных приведений и хуже читаются. Type switch явно перечисляет поддерживаемые варианты, проверяется компилятором и позволяет работать с конкретными полями внутри соответствующей ветви.
Практический выбор — type switch с отдельными case для конкретных типов и резервной ветвью для интерфейса или default. Это даёт предсказуемое поведение и не скрывает список поддерживаемых событий.
1. Что произойдёт с типом переменной при перечислении нескольких конкретных типов в одном case?
Переменная не получает тип каждого из перечисленных типов. Она имеет статический тип исходного интерфейсного выражения, потому что компилятор не может безопасно предоставить поля, существующие только у одного из вариантов. Для доступа к уникальным данным такие типы следует разнести по отдельным ветвям.
2. Как обрабатывается значение nil в type switch?
Если интерфейсное значение действительно равно nil, срабатывает специальный case nil. При этом переменная, объявленная в type switch, имеет тип исходного интерфейсного выражения и содержит nil. Интерфейс, внутри которого находится типизированный nil-указатель, не является nil-интерфейсом, поэтому case nil для него не сработает.
3. Какая ветвь выбирается, если значение удовлетворяет нескольким интерфейсным case?
Выбирается первая подходящая ветвь в порядке её записи. Компилятор не выбирает автоматически более узкий или более «подходящий» интерфейс. Поэтому case с конкретным интерфейсом или специальным поведением нужно располагать до общего интерфейсного case, иначе общий вариант перехватит значение раньше.