Зачем в Go нужны дженерики и какие проблемы они решают?

Ответ

Дженерики (generics), появившиеся в Go 1.18, позволяют писать функции и структуры данных, которые могут работать с любыми типами, сохраняя при этом строгую проверку типов на этапе компиляции.

Основные проблемы, которые решают дженерики:


  1. Избавление от дублирования кода. Раньше для работы с разными типами приходилось писать отдельные функции для каждого из них (например, SortInts, SortStrings). Дженерики позволяют создать одну универсальную функцию.


  2. Типобезопасность без interface{}. До дженериков для написания обобщенного кода использовали interface{}. Это приводило к двум проблемам:

    • Потеря статической проверки типов: приходилось использовать утверждения типа (value.(string)), которые могли вызвать панику в рантайме.
    • Снижение производительности из-за упаковки (boxing) значений в интерфейсы.

Пример универсальной функции Map:

// T и U — это параметры типа (type parameters).
// any — это ограничение (constraint), означающее "любой тип".
func Map[T, U any](input []T, f func(T) U) []U {
    output := make([]U, len(input))
    for i, v := range input {
        output[i] = f(v)
    }
    return output
}

// Пример использования:
numbers := []int{1, 2, 3}
// Компилятор сам выводит типы, код типобезопасен.
strings := Map(numbers, strconv.Itoa) // Результат: []string{"1", "2", "3"}

Таким образом, дженерики делают код более чистым, переиспользуемым и безопасным, устраняя необходимость в бойлерплейте и небезопасных преобразованиях типов.