Определите ошибку компиляции и объясните её причину. Зачем интерфейс Number можно передать как ограничение, но нельзя использовать как тип переменной?
package main
type Number interface {
~int | ~float64
}
func Sum[T Number](a, b T) T {
return a + b
}
func main() {
var x Number
_ = x
}
Код не скомпилируется: Number — это интерфейс с множеством типов, предназначенный только для ограничения параметра типа. Такой интерфейс нельзя использовать как обычный тип переменной или значение.
Number допустим в объявлении Sum[T Number], потому что там он задаёт множество разрешённых типов: int и типы с базовым типом int, либо float64 и типы с базовым типом float64.
До появления обобщений интерфейсы Go в основном описывали поведение через набор методов и могли использоваться как обычные типы: переменные, аргументы и результаты функций. Это решало задачу полиморфизма во время выполнения.
С появлением generics в Go 1.18 интерфейсы получили дополнительную роль: они стали описывать множества типов и допустимые операции над параметрами типа. Например, запись ~int | ~float64 задаёт не методы, а набор типов, разрешённых для подстановки в T.
Разработчик может предположить, что любой интерфейс можно использовать как тип переменной. Для обычного интерфейса с методами это верно, но интерфейс с объединением типов не определяет единственный динамический контракт, который можно представить обычным интерфейсным значением.
Если разрешить var x Number, возникла бы неоднозначность: интерфейс должен был бы хранить значение любого типа из множества, но операции и правила его использования определены здесь как ограничение параметра типа, а не как обычный runtime-интерфейс.
Интерфейс Number является небазовым интерфейсом: он содержит элементы множества типов — ~int и ~float64. Такие интерфейсы разрешено использовать только в качестве ограничений параметров типа.
Символ ~ означает любой именованный тип с указанным базовым типом. Поэтому Number допускает, например, int, float64, а также пользовательские типы вроде type Celsius float64.
В объявлении функции компилятор подставляет конкретный тип вместо T и проверяет, входит ли он в множество, заданное Number:
После подстановки T становится конкретным типом, поэтому операция + проверяется статически. Но объявление var x Number запрещено: Number не является обычным интерфейсным типом для хранения значения.
Если требуется хранить значения разных типов во время выполнения, используют any или обычный интерфейс с методами. Однако это переносит проверку допустимых типов с этапа компиляции на runtime и может потребовать type switch или type assertion.
Команда проектирует функцию агрегации чисел. Рассматривались три варианта.
any и разбирать значения через type switch. Плюс — можно передавать разнородные значения во время выполнения. Минусы — потеря статической типизации, ручная обработка ошибок и возможность получить неподдерживаемый тип только при запуске.Float64() float64. Плюс — единый runtime-контракт. Минус — каждому числовому типу нужны адаптеры или методы, а исходные типы нельзя использовать напрямую.Number и обобщённую функцию. Плюсы — проверка допустимых типов компилятором, сохранение конкретного типа результата и отсутствие ручного разбора типов.Для операции над однородным набором чисел выбран третий вариант. Он не пытается создать переменную типа Number, а применяет ограничение только к параметру типа. В результате ошибки переданного типа обнаруживаются при компиляции, а функция остаётся типобезопасной.
Можно ли заменить Number на any, а затем выполнить x + y?
Нет. Значения типа any не поддерживают произвольные операции без предварительного определения конкретного типа. Нужно выполнить type switch или assertions и отдельно обработать числовые варианты. В отличие от Number в generic-функции, any не сообщает компилятору, что операция + допустима для значения.
Почему интерфейс только с методами можно использовать как переменную, а Number — нельзя?
Интерфейс вроде interface { String() string } является базовым интерфейсом. Он описывает runtime-значение через динамический тип и набор методов, поэтому его можно хранить в переменной и передавать между функциями.
Number описывает type set — множество типов, из которого компилятор выбирает конкретный тип-подстановку для параметра. Это compile-time-ограничение, а не runtime-контракт, поэтому использовать его как обычный тип нельзя.
Как принять коллекцию значений разных числовых типов, если []Number запрещён?
Нужно выбрать модель данных явно. Для однородной коллекции используют generic-функцию, например func Sum[T Number](values []T) T; тогда все элементы имеют один конкретный тип T.
Если коллекция действительно должна содержать одновременно int и float64, используют []any либо собственный обычный интерфейс-обёртку с методами. Это позволяет хранить разнородные значения, но требует runtime-проверок и обычно усложняет арифметику. Поэтому обобщённый вариант предпочтителен, когда однородность элементов возможна.