С введением дженериков (с версии Go 1.18) можно писать функции и типы, которые работают с различными типами данных, обеспечивая при этом строгую типизацию и переиспользование кода.
Пример кода
package main import "fmt" // Обобщённая функция Swap меняет местами два значения любого типа. // T — параметр типа, а ограничение any означает, что функция принимает аргументы любого типа. func Swap[T any](a, b T) (T, T) { return b, a } // Обобщённый тип Box хранит значение произвольного типа T. type Box[T any] struct { value T } // Метод Get возвращает значение, хранящееся в Box. func (b Box[T]) Get() T { return b.value } // Метод Set присваивает новое значение полю value. func (b *Box[T]) Set(value T) { b.value = value } func main() { // Пример использования функции Swap с числами a, b := 10, 20 a, b = Swap(a, b) fmt.Println("После обмена:", a, b) // Пример использования типа Box для хранения значения int intBox := Box[int]{value: 100} fmt.Println("Значение в intBox:", intBox.Get()) // Пример использования типа Box для хранения значения string strBox := Box[string]{value: "hello"} fmt.Println("Значение в strBox:", strBox.Get()) }
Как это работает
-
Параметры типа и ограничения
В объявлении функцииSwap[T any]
параметр типаT
записывается в квадратных скобках сразу после имени функции. Ограничениеany
(это синоним дляinterface{}
) указывает, что типT
может быть любым. Таким образом, функцияSwap
способна принимать на вход значения любых типов, например,int
,string
и другие. -
Обобщённый тип
ТипBox[T any]
представляет контейнер, способный хранить значение любого типа. МетодыGet
иSet
работают с этим значением, не теряя информации о типе. При создании экземпляра типаBox
компилятор подставляет конкретный тип, например,Box[int]
илиBox[string]
. -
Компиляция и безопасность типов
При вызове обобщённой функции или создании обобщённого типа компилятор Go подставляет конкретный тип вместо параметраT
. Это позволяет избежать дублирования кода и одновременно гарантирует, что типизация будет проверяться на этапе компиляции. -
Преимущества дженериков
Благодаря обобщённым типам и функциям можно писать более универсальный и переиспользуемый код, не прибегая к использованию небезопасных преобразований типов и интерфейсов. Это увеличивает читаемость и поддерживаемость кода, снижая количество ошибок.
Компилятор Go создаёт отдельную, специализированную версию функции (или типа) для каждого уникального набора типовых параметров, используемых в коде. Такой подход называется монопорфизацией.
Разъяснения
-
Монопорфизация:
При компиляции для каждой инстанциации обобщённого кода создаётся конкретный вариант с подставленным типом. Например, если функцияSwap[T any]
вызывается дляint
и дляstring
, то компилятор сгенерирует две версии — одну дляint
, другую дляstring
. -
Преимущества:
Такой механизм позволяет:-
Оптимизировать код под конкретные типы, что может улучшать производительность.
-
Сохранять строгую типизацию и выявлять ошибки на этапе компиляции.
-
Избегать накладных расходов, связанных с преобразованиями типов во время выполнения.
-
-
Возможные нюансы:
Хотя для каждой инстанциации создаётся своя версия, компилятор может применять оптимизации (например, инлайнинг или другие методы оптимизации кода), что помогает снизить потенциальное увеличение размера бинарника.
Таким образом, реализация дженериков в Go через монопорфизацию делает возможным написание обобщённого кода без потери производительности и безопасности типов.