Go: Дженерики

Теория: Обобщённые структуры и методы

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

type Box[T any] struct {
	value T
}

Здесь Box — это контейнер, который может хранить значение любого типа. Если создать Box[int], внутри окажется число, а если Box[string] — строка. Такой подход избавляет от необходимости использовать пустой интерфейс и делать ручное приведение типов.

Структуры могут иметь методы, которые тоже используют параметры типа. Например, у коробки можно добавить методы для установки и получения значения:

func (b *Box[T]) Set(v T) {
	b.value = v
}

func (b *Box[T]) Get() T {
	return b.value
}

Эти методы сохраняют строгую типизацию: Box[int] принимает и возвращает только числа, а Box[string] — только строки. Попытка смешать типы приведёт к ошибке компиляции.

Иногда нужно работать сразу с несколькими типами. В таком случае объявляют несколько параметров. Например, пара ключ-значение:

type Pair[K, V any] struct {
	Key   K
	Value V
}

Такая структура позволяет строить словари, кэш или ассоциированные данные, где один параметр отвечает за тип ключа, а другой — за тип значения.

Хороший пример обобщённой структуры — стек. Он хранит элементы в срезе и предоставляет методы для добавления и извлечения:

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) {
	if len(s.items) == 0 {
		var zero T
		return zero, false
	}
	last := s.items[len(s.items)-1]
	s.items = s.items[:len(s.items)-1]
	return last, true
}

Такой стек можно использовать с любым типом:

ints := Stack[int]{}
ints.Push(10)
ints.Push(20)
val, ok := ints.Pop() // вернёт 20

strings := Stack[string]{}
strings.Push("go")
strings.Push("generics")
s, _ := strings.Pop() // вернёт "generics"

Те же принципы можно применить для очереди, дерева или связанного списка. Например, очередь:

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

func (q *Queue[T]) Enqueue(v T) {
	q.items = append(q.items, v)
}

func (q *Queue[T]) Dequeue() (T, bool) {
	if len(q.items) == 0 {
		var zero T
		return zero, false
	}
	first := q.items[0]
	q.items = q.items[1:]
	return first, true
}

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

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845