Урок по треку Go 6 мин

Дженерики в Go

Параметры типов, ограничения, comparable и cmp.Ordered, вывод типов, когда дженерики не нужны.

Дженерики появились в Go 1.18. Они позволяют писать функции и типы, параметризованные типами, без interface{} и приведения типов.

Функции с параметрами типов

func Map[T, U any](s []T, f func(T) U) []U {
    res := make([]U, 0, len(s))
    for _, v := range s {
        res = append(res, f(v))
    }
    return res
}

lens := Map([]string{"a", "bb"}, func(s string) int { return len(s) }) // [1 2]

Типы обычно выводятся из аргументов. Явно: Map[string, int](...).

Ограничения

Ограничение это интерфейс. Помимо методов, в нём может быть набор типов:

type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](xs []T) T {
    var s T
    for _, x := range xs { s += x }
    return s
}

~int означает «любой тип, у которого базовый тип int», например type Celsius int. Без тильды подойдёт только сам int.

Встроенные ограничения:

  • any: любой тип.
  • comparable: можно ==. Нужен для ключей мапы.
  • cmp.Ordered (Go 1.21+): можно <. Числа и строки.

Интерфейс с набором типов можно использовать только как ограничение, не как тип переменной.

Обобщённые типы

type Stack[T any] struct{ items []T }

func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 { return zero, false }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

Нулевое значение параметра типа получают через var zero T.

Ограничения языка

  • У методов не может быть своих параметров типов: только у типа или у функции.
  • Нельзя сделать type switch прямо по T, только по значению, приведённому к any.
  • Специализаций и перегрузки нет.

Стандартная библиотека

Пакеты slices и maps (Go 1.21) дают обобщённые slices.Sort, slices.Contains, slices.Index, maps.Keys, cmp.Compare, функции min и max встроены в язык. С Go 1.23 появились итераторы iter.Seq[V] и range по функциям, а maps.Keys возвращает итератор. В Go 1.24 разрешены обобщённые псевдонимы типов.

Как это компилируется

Go использует смешанный подход GC shape stenciling: одна копия кода на «форму» типа (все указатели имеют одну форму) плюс словарь с информацией о типах. Поэтому дженерик через указатели может работать чуть медленнее, чем код под конкретный тип, но быстрее, чем через interface{} с приведением.

Когда не нужны

Если функции нужно только вызвать методы, достаточно обычного интерфейса: func Print(s fmt.Stringer). Дженерики хороши для контейнеров и алгоритмов над слайсами и мапами.

Что спросят на собесе

  • Что такое ограничение типа, зачем ~.
  • Чем comparable отличается от any, а any от interface{} (ничем, псевдоним).
  • Почему у метода не может быть параметров типа.
  • Когда дженерик лучше интерфейса и наоборот.
  • Как реализованы дженерики: мономорфизация или словари.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.