# Дженерики в Go

> Параметры типов в Go, появившиеся в версии 1.18 (2022), — обобщённые функции и типы с ограничениями через интерфейсы; пакеты slices и maps, итераторы и правила, когда дженерики не нужны.

Источник: https://llmdocs.ru/wiki/go-generics/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Появились**: Go 1.18 (март 2022)
- **Предложение**: Type Parameters Proposal (Иан Лэнс Тейлор, Роберт Гриземер)
- **Синтаксис**: func Name[T Constraint](x T)
- **Ограничения**: интерфейсы, в том числе наборы типов (~int | ~float64)
- **Встроенные ограничения**: any, comparable
- **Стандартные пакеты**: slices, maps, cmp, iter
**Дженерики в Go** (параметры типов, англ. *type parameters*) — возможность писать функции и типы, которые работают с разными типами данных, сохраняя статическую проверку. Они появились в [Go](https://llmdocs.ru/wiki/go.md) 1.18 в марте 2022 года — после более чем десяти лет обсуждений и нескольких отвергнутых проектов — и стали самым крупным изменением языка с версии 1.0.[1][3]

## Зачем

До дженериков обобщённый код в Go писали тремя способами, у каждого из которых есть недостатки:

- **копированием** — `SumInts`, `SumFloats`, `SumInt64s`;
- через **пустой интерфейс** `interface{}` — с потерей проверки типов и приведением в рантайме;
- **генерацией кода** (`go generate`).

С параметрами типов одна функция подходит для всех нужных типов, а ошибки ловит компилятор.

## Синтаксис

Параметры типов объявляются в квадратных скобках перед обычными параметрами. Каждый параметр имеет **ограничение** (*constraint*) — интерфейс, описывающий допустимые типы:[2]

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

lengths := Map([]string{"go", "rust"}, func(s string) int { return len(s) })
// [2 4] — T и U выведены компилятором
```

В большинстве вызовов аргументы типов **выводятся** автоматически; явно их пишут, когда вывести нельзя: `Map[string, int](...)`.

## Ограничения и наборы типов

Интерфейс в Go 1.18 стал описывать не только набор методов, но и **набор типов**:[5]

```go
type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

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

- `|` перечисляет допустимые типы;
- `~int` означает «любой тип, чьё базовое представление — `int`», включая пользовательские вроде `type UserID int`;
- `any` — синоним `interface{}`, любой тип;
- `comparable` — типы, поддерживающие `==` и `!=`; нужен для ключей `map`;
- пакет `cmp` предоставляет `cmp.Ordered` — типы с операторами `<`, `>`.

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

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

```go
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 st Stack[string]
st.Push("a")
```

Ограничение языка: **методы не могут иметь собственных параметров типов** — только использовать параметры своего типа. Поэтому цепочки вида `stream.Map(...).Filter(...)` с меняющимся типом в Go пишут обычными функциями.

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

Дженерики позволили добавить в [стандартную библиотеку](https://llmdocs.ru/wiki/go-stdlib.md) типобезопасные утилиты:[7]

```go
import ("slices"; "maps")

xs := []int{3, 1, 2}
slices.Sort(xs)                       // [1 2 3]
i, found := slices.BinarySearch(xs, 2)
slices.Contains(xs, 5)                // false
keys := slices.Sorted(maps.Keys(m))   // отсортированные ключи map
```

В Go 1.23 появились **итераторы** — функции вида `iter.Seq[T]`, по которым можно проходить обычным `for range`; они тоже обобщённые.[6] `sync.Map` и `atomic.Pointer[T]` — другие примеры обобщённых типов в библиотеке.

## Реализация

Компилятор Go использует компромисс между полной мономорфизацией (как в Rust и C++, где для каждого типа генерируется отдельная копия кода) и упаковкой в интерфейсы (как в Java): функции компилируются по одной копии на **«форму»** типа (GC shape) — например, одна копия на все указатели — с передачей словаря с информацией о типе. Это ускоряет компиляцию и уменьшает размер бинарника, но обобщённый код иногда работает чуть медленнее, чем написанный вручную для конкретного типа. В Rust ту же задачу решают [трейты](https://llmdocs.ru/wiki/rust-traits.md) с мономорфизацией.

## Когда использовать

Авторы Go советуют не превращать дженерики в инструмент по умолчанию:[4]

- **Использовать** — для функций над срезами, map и каналами любого типа элемента; для обобщённых структур данных (стеки, очереди, деревья, кэши); когда иначе пришлось бы копировать одну и ту же реализацию.
- **Не использовать** — если достаточно обычного интерфейса с методами (`io.Reader` лучше, чем `[T io.Reader]`); если реализация различается для разных типов; если код становится сложнее читать.

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

## См. также

- [Go (язык)](https://llmdocs.ru/wiki/go.md)
- [Стандартная библиотека Go](https://llmdocs.ru/wiki/go-stdlib.md)
- [Типизация в Python (type hints, PEP 484)](https://llmdocs.ru/wiki/type-hints.md)
- [Go vs Rust — сравнение](https://llmdocs.ru/wiki/go-vs-rust.md)

## Источники

1. [Go — Type Parameters Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md)
2. [Go Tutorial — Getting started with generics](https://go.dev/doc/tutorial/generics)
3. [Go Blog — An Introduction To Generics](https://go.dev/blog/intro-generics)
4. [Go Blog — When To Use Generics](https://go.dev/blog/when-generics)
5. [The Go Programming Language Specification — Type parameter declarations](https://go.dev/ref/spec#Type_parameter_declarations)
6. [Go Blog — Range Over Function Types](https://go.dev/blog/range-functions)
7. [Go — package slices](https://pkg.go.dev/slices)
