С введением дженериков (с версии 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 через монопорфизацию делает возможным написание обобщённого кода без потери производительности и безопасности типов.