Включение или отключение дженериков в язык Go было давней дискуссией и поводом для драмы с момента первого появления языка в 2009 году. В долгие дискуссии о том, хороши ли дженерики или плохи, вылились реки чернил. язык должен их поддерживать или нет. До сих пор команда Go решила оставить дженерики за пределами языка.

Однако с анонсом второй версии языка команда Go начала обсуждение, чтобы добавить к языку универсальные шаблоны. Были разные черновики о том, как добавить дженерики, наконец, оседая в том, что кажется окончательным черновиком дизайна.

Дженерики в маркированном списке

Проект довольно длинный, но не такой плотный, как можно было бы ожидать от такого документа. Важно подчеркнуть, что цель команды Go - создать обратно совместимый дизайн, который отвечает потребностям людей в универсальных шаблонах, не усложняя язык более сложным, чем это необходимо.

Спецификацию можно резюмировать в следующих пунктах, которые мы разработаем позже. Тем не менее, всем, кто интересуется этой темой, рекомендуется прочитать полный документ, ссылка на который находится внизу статьи. Мы предполагаем, что читатель знаком с основными концепциями Go, такими как функции, типы и интерфейсы.

  • Функции и типы могут иметь дополнительный список параметров типа перед обычными с использованием квадратных скобок для обозначения используемых универсальных типов. Эти параметры типа можно использовать как любой другой параметр в остальной части определения и тела.
  • Параметры типа определяются с помощью ограничений, которые являются типами интерфейса. Ограничения определяют необходимые методы и типы, разрешенные для аргумента типа, и описывают методы и операции, доступные для универсального типа.
  • Мы можем использовать вывод типа, который часто позволяет опускать аргументы типа.
  • У нас есть специальное ограничение с именем any, которое ведет себя аналогично interface{}, и новый пакет с именем constraints, который будет иметь обычно используемые ограничения.

Определение и использование общих функций

Функции могут принимать дополнительный список параметров, заключенный в квадратные скобки вместо скобок, и их можно использовать как часть определения и / или тела. Эти параметры типа ведут себя как обычные параметры и подчиняются тем же правилам, что и традиционные параметры. Эти параметры будут иметь «интерфейс» с именем «ограничение» в качестве типа или специальный интерфейс any. Типы также могут иметь параметры типа, если они определены.

// https://go2goplay.golang.org/p/Z9e7O8ony21
type queue[T any] []T
q := new(queue[int])

Параметры типа функции могут использоваться в теле функции, а типы, использующие параметры типа, могут использоваться в модуле без ограничений . Требование для их использования - передать аргумент типа, который соответствует интерфейсу. Это похоже на присвоение переменной типа интерфейса: аргумент типа должен реализовывать ограничения параметра типа, а общий код может использовать только операции, разрешенные для ограничения или разрешенные для любого типа.

// https://go2goplay.golang.org/p/s8JCfu5qBKz
func Print[T any](s []T) {
    for _, v := range s {
        fmt.Print(v)  
    }
}
strings := []string{"Hello ", "world"}
Print[string](strings)
// Output: "Hello world"
nums := []int{1,2,3}
Print[int](nums)
// Output: 123

Определение ограничений

Ограничения - это интерфейсы. Вы можете ожидать тех же функций интерфейсов в ограничениях, как встраивание одного ограничения в другое. У ограничений есть одно дополнение к интерфейсам. В ограничениях можно явно указать типы, которые могут использоваться в качестве аргументов типа, с помощью ключевого слова type, за которым следует список типов, разделенных запятыми.

// https://go2goplay.golang.org/p/qWeRkYjjtKP
type SignedInteger interface {
	type int, int8, int16, int32, int64
}

Ограничения можно определять и экспортировать как библиотеки, и команда Go заявила, что они, вероятно, определят и экспортируют новую стандартную библиотеку с предварительным названием constraints, которая будет содержать часто используемые ограничения.

Ограничение «любое»

Тип any подобен типу interface{}. Это ограничение, которое может выполнить любой тип. Тем не менее, в нем все еще есть несколько правил, которые необходимо соблюдать. После того, как вы назначили ему тип, вам нужно продолжать использовать тот же тип. Вы можете объявлять переменные, передавать или возвращать переменные другим функциям, принимать адреса… но всегда одного типа. Вы можете преобразовать или присвоить значения этих типов типу interface{} и использовать утверждение типа для их обратного преобразования. Вы также можете использовать тип как случай в переключателе типа.

// Vector is a name for a slice of any element type.
type Vector[T any] []T
func (v *Vector[T]) Push(x T) { *v = append(*v, x) }
// T is []int
var v Vector[int]
// func (v *Vector[int]) Push(x int) { *v append(*v, int) }
v.Push(1)
// Output: [1]

Вывод типа в дженериках

Во многих случаях мы можем использовать вывод типа, чтобы избежать явной записи некоторых или всех аргументов типа. Если вы передаете аргумент функции и от него зависят другие типы, они могут быть выведены без явного указания этого. Это работает так же, как когда вы используете переменную определенного типа в качестве параметра вместо функции, которая имеет интерфейс в качестве аргумента.

func Map[F, T any](s []F, f func(F) T) []T { ... }  
var s []int 
f := func(i int) int64 { return int64(i) } 
var r []int64
// Can be used in all these ways
// Specify both type arguments explicitly. 
r = Map[int, int64](s, f) 
// Specify just the first type argument, for F, 
// and let T be inferred. 
r = Map[int](s, f)
// Don't specify any type arguments, and let both be inferred.
r = Map(s, f)

Эта часть становится чрезвычайно запутанной и очень быстро, поэтому я не могу перестать рекомендовать реальную всю спецификацию, в которой этот раздел объясняется очень подробно. Важно понимать, что во многих случаях нам не нужно явно указывать все параметры типа.

Заявление об ограничении ответственности и ссылки

Эта статья основана на следующей статье, цепочке и документе. Поскольку это черновик, он может измениться с момента публикации статьи. Чтобы гарантировать исправление во всех примерах, они были извлечены из спецификации или с игровой площадки Go 2, куда вы уже можете отправиться, если хотите протестировать их самостоятельно.