Интерфейсы в Go: рефлексия (reflection)
Информация о динамическом типе, хранящаяся в значении интерфейса, может использоваться для проверки динамического значения интерфейса и управления значениями, на которые ссылается динамическое значение. В программировании это называется отражением.
Мы не будем рассматривать функции, предоставляемые стандартным reflect пакетом. Ниже будут представлены только встроенные функции рефлексии в Go. В Go встроенная рефлексия достигается с помощью утверждений типов (type assertion) и type-switch блоков кода потока управления.
Type Assertion
В Go есть четыре типа случаев преобразования значений, связанных с интерфейсом:
-
Преобразовать неинтерфейсное значение в интерфейсное значение, где тип не интерфейсного значения должен реализовывать тип интерфейсного значения.
-
Преобразовать значение интерфейса в значение интерфейса, где тип исходного значения интерфейса должен реализовывать тип значения целевого интерфейса.
-
Преобразовать значение интерфейса в значение, не являющееся интерфейсом, где тип значения, не являющегося интерфейсом, должен реализовывать тип значения интерфейса.
-
Преобразовать значение интерфейса в значение интерфейса, где тип значения исходного интерфейса не реализует тип интерфейса назначения, но динамический тип значения исходного интерфейса может реализовать тип интерфейса назначения.
Мы уже рассмотрели первые два вида случаев. Оба они требуют, чтобы тип исходного значения реализовывал тип интерфейса назначения. Конвертируемость первых двух проверяется во время компиляции.
Далее будут объяснены последние два вида случаев. Конвертируемость для последних двух проверяется во время выполнения с помощью синтаксиса, называемого type assertion. Синтаксис также применим ко второму типу преобразования в списке выше.
Форма выражения утверждения типа: i.(T), где i — значение интерфейса, а T — имя типа или литерал типа. Тип T может быть:
-
произвольным неинтерфейсным типом;
-
произвольным типом интерфейса.
В type assertion i.(T) называется i asserted value, а T называется asserted type. Type assertion может быть успешным или неудачным.
-
В случае T неинтерфейсного типа, если динамический тип i существует и идентичен T, то утверждение будет выполнено успешно, в противном случае type assertion завершится ошибкой. Когда type assertion выполняется успешно, результатом оценки assertion является копия динамического значения i. Мы можем рассматривать type assertion такого рода как попытки распаковки значения.
-
В случае типа T интерфейса, если динамический тип i существует и реализует T, type assertion будет выполнено успешно, в противном случае завершится ошибкой. Когда type assertion выполняется успешно, копия динамического значения i будет заключена в T значение, и это T значение будет использоваться в качестве результата оценки утверждения.
Когда утверждение типа не выполняется, результатом его оценки является нулевое значение утвержденного типа.
По правилам, описанным выше, если утвержденное значение в утверждении типа является нулевым значением интерфейса, то утверждение всегда будет давать сбой.
В большинстве сценариев утверждение типа используется как выражение с одним значением. Однако когда утверждение типа используется в качестве единственного выражения исходного значения в присваивании, оно может привести ко второму необязательному нетипизированному логическому значению и рассматриваться как выражение с несколькими значениями. Второе необязательное нетипизированное логическое значение указывает, успешно ли выполняется утверждение типа.
Обрати внимание: если утверждение типа не выполняется и утверждение типа используется как выражение с одним значением (второй необязательный логический результат отсутствует), то произойдет паника.
Пример, который показывает, как использовать утверждения типа (утверждаемые типы не являются интерфейсными типами).

Другой пример, показывающий, как использовать утверждения типа (утверждаемые типы являются типами интерфейса).

Фактически для значения интерфейса i с динамическим типом T вызов метода i.m(...) эквивалентен вызову метода i.(T).m(...).
type-switch блок управления потоком
Синтаксис type-switch блока кода может быть самым странным синтаксисом в Go. Его можно рассматривать как расширенную версию утверждения типа. Кодовый type-switch блок в чем-то похож на switch-case кодовый блок потока управления. Это выглядит так:

Эта aSimpleStatement; часть не является обязательной в type-switch кодовом блоке. aSimpleStatement должно быть простое утверждение. x должно быть значением интерфейса, и оно называется утвержденным значением. v называется результатом утверждения, он должен быть представлен в форме короткого объявления переменной.
За каждым case ключевым словом в type-switch блоке может следовать предварительно объявленный nil идентификатор или список, разделенный запятыми, состоящий как минимум из одного имени типа и литерала типа. Ни один из таких элементов: nil, имена типов и литералы типов — не может дублироваться в одном и том же type-switch блоке кода.
Если тип, обозначенный именем типа или литералом типа, следующим за case ключевым словом в type-switch блоке кода, не является типом интерфейса, то он должен реализовывать тип интерфейса утвержденного значения.
Вот пример, в котором используется type-switch блок кода потока управления.

Выход:

Приведенный выше пример эквивалентен следующему в логике:

type-switch кодовые блоки подобны switch-case кодовым блокам в некоторых аспектах.
-
Как и в switch-case блоках, в type-switch блоке кода может быть не более одной default ветви.
-
Подобно switch-case блокам, в type-switch кодовом блоке, если присутствует default ветвь, это может быть последняя, первая или средняя ветвь.
-
Как и switch-case блоки, type-switch блок кода может не содержать никаких ветвей, он будет рассматриваться как незадействованный.
Но, в отличие от switch-case блоков кода, fallthrough операторы нельзя использовать в блоках ветвей блока type-switch кода.