Ответ
Дженерики (обобщенное программирование) в Go, появившиеся в версии 1.18, позволяют писать функции и типы данных, которые могут работать с любым типом из определенного набора. Это устраняет необходимость дублирования кода для разных типов, сохраняя при этом строгую статическую типизацию.
Основные концепции
- Параметры типа (Type Parameters): Объявляются в квадратных скобках после имени функции или типа. Например,
[T any]
. - Ограничения типа (Type Constraints): Это интерфейсы, которые определяют, какими свойствами должен обладать тип-аргумент. Например,
any
(разрешает любой тип),comparable
(разрешает типы, которые можно сравнивать с помощью==
и!=
), или пользовательские интерфейсы.
Пример: обобщенная функция
// SumNumbers суммирует значения в map.
// K - должен быть сравним (comparable), V - либо int64, либо float64.
func SumNumbers[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
// Использование:
ints := map[string]int64{"a": 1, "b": 2}
floats := map[string]float64{"a": 1.5, "b": 2.5}
fmt.Println(SumNumbers(ints)) // Вывод: 3
fmt.Println(SumNumbers(floats)) // Вывод: 4.0
Особенности и ограничения
-
Использование методов: Вы можете вызывать методы у переменных обобщенного типа, только если эти методы определены в его ограничении (интерфейсе).
type Stringer interface { String() string } func Print[T Stringer](val T) { fmt.Println(val.String()) // OK }
- Нет специализации: В отличие от C++, в Go нет возможности предоставить отдельную, более эффективную реализацию дженерик-функции для конкретного типа.
- Реализация: Go не использует простое стирание типов (type erasure), как в Java. Компилятор может генерировать разные реализации для разных категорий типов (например, одна для всех указателей, другая для конкретной структуры), что обеспечивает хорошую производительность.
- Операторы: Операторы (
+
,*
,>
и т.д.) можно использовать только если они разрешены ограничением типа (например,+
для числовых типов,==
дляcomparable
).