Ответ
Да, конечно. Дженерики (generics), или обобщённое программирование, появились в Go версии 1.18. Они позволяют писать функции и структуры данных, которые могут работать с любым типом, удовлетворяющим определённым ограничениям (constraints).
Основные концепции:
- Параметры типа (Type Parameters): Объявляются в квадратных скобках после имени функции или типа.
[T any]
- Ограничения (Constraints): Это интерфейсы, которые определяют, какие операции должен поддерживать тип, чтобы его можно было использовать в качестве аргумента типа. В Go есть два встроенных ограничения:
any
: псевдоним дляinterface{}
, разрешает любой тип.comparable
: разрешает типы, которые поддерживают операции сравнения==
и!=
. Также можно определять свои собственные ограничения:type Number interface { int | float64 }
.
Примеры использования:
-
Обобщённые функции:
// Map применяет функцию f к каждому элементу слайса. func Map[T, U any](slice []T, f func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = f(v) } return result }
-
Обобщённые типы (структуры):
// Stack - простая реализация стека для любого типа. type Stack[T any] struct { items []T } func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T // Возвращаем zero-value для типа T return zero, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item, true }
Ограничения и особенности:
- Нет специализации: В отличие от C++, в Go нельзя предоставить отдельную реализацию обобщённой функции для конкретного типа.
- Ограничения на методы: Нельзя объявлять методы с параметрами типа. То есть, дженериками могут быть функции и типы, но не методы.
- Производительность: Компилятор Go создаёт отдельную реализацию функции для каждого конкретного типа, используемого с дженериком. Это обеспечивает высокую производительность во время выполнения (runtime), но может увеличить размер бинарного файла.
Я использую дженерики в основном для написания утилитарных функций (работа с коллекциями, как в примере Map
) и для создания типобезопасных структур данных (стеки, очереди, деревья). Это помогает избежать дублирования кода и ошибок, связанных с приведением типов interface{}
.