В type switch динамическое значение удовлетворяет нескольким интерфейсным case: по какому правилу Go выбирает ветку?
Go выбирает первый подходящий case сверху вниз. Если динамический тип значения реализует несколько интерфейсов, порядок интерфейсных case в type switch определяет результат.
Конкретный тип проверяется на точное совпадение с динамическим типом, а интерфейсный case — на реализацию этого интерфейса динамическим типом.
Интерфейсы Go позволяют работать со значением через набор методов, не раскрывая его конкретный тип. Это удобно для слабой связанности компонентов, но иногда программе всё же нужно выбрать поведение в зависимости от фактического типа значения.
Type switch решает эту задачу декларативно: он объединяет проверку динамического типа и выполнение соответствующей ветки. Такой подход безопаснее ручного приведения типов, потому что неподходящий вариант не вызывает паники.
Один конкретный тип может реализовывать несколько интерфейсов. Поэтому несколько case в одном type switch способны одновременно соответствовать одному и тому же значению.
Если порядок веток выбран неудачно, более общий интерфейс перехватит значение раньше специализированного. В результате код может выполнить не ту обработку, которую ожидал разработчик, хотя программа успешно компилируется.
Проверки выполняются в порядке их записи. Go останавливается на первом совпадении; остальные case для этого значения не рассматриваются.
Тип packet реализует и fmt.Stringer, и io.Reader, но будет выбрана первая ветка — fmt.Stringer. Перестановка case даст другой результат.
Внутри интерфейсной ветки переменная v имеет тип соответствующего интерфейса. В ветке конкретного типа она получает этот конкретный тип, поэтому доступны его методы и операции, разрешённые для этого типа.
Обычно более специфичные проверки помещают выше более общих. Однако Go не определяет формальную иерархию специфичности интерфейсов: порядок должен быть выбран разработчиком явно.
Сервис обрабатывает поток событий, представленных значением any. Некоторые события одновременно поддерживают интерфейсы сериализации и потокового чтения. Если сначала проверять общий интерфейс чтения, события будут уходить в потоковую обработку, даже когда для них предусмотрена специализированная сериализация.
Возможны несколько решений:
Практически выбирают первый вариант, если пересечение интерфейсов осознанно и порядок документирован. Если порядок критичен для бизнес-логики, предпочтительнее переработать контракт или вынести приоритет в отдельный диспетчер, чтобы не скрывать его в расположении case.
1. Что произойдёт с nil-интерфейсом в type switch?
Если интерфейсное значение действительно равно nil, сработает специальный case nil, если он присутствует. Интерфейсный case не совпадёт, потому что у такого значения отсутствует динамический тип.
Это отличается от интерфейса, содержащего типизированный nil-указатель. У него динамический тип существует, поэтому может сработать case для этого типа или интерфейса, который он реализует; case nil при этом не выбирается.
2. Можно ли считать интерфейсный case более общим автоматически?
Нет. Go не переставляет case по степени специализации и не анализирует намерение разработчика. Если два интерфейса подходят, выигрывает тот, который записан раньше.
Поэтому добавление нового интерфейсного case в начало switch может изменить поведение уже существующего кода без изменения типов. Порядок веток нужно рассматривать как часть логики обработки.
3. Какой тип имеет переменная, объявленная в type switch?
В ветке с конкретным типом переменная имеет этот конкретный тип. В ветке с интерфейсом она имеет тип указанного интерфейса, даже если фактическое значение реализует дополнительные методы.
Следовательно, через переменную интерфейсной ветки доступны только методы этого интерфейса. Чтобы использовать дополнительные возможности, нужна отдельная проверка или более подходящий конкретный case.