Ответ
Дженерики (generics), появившиеся в Go 1.18, позволяют писать функции и структуры данных, которые могут работать с любыми типами, сохраняя при этом строгую проверку типов на этапе компиляции.
Основные проблемы, которые решают дженерики:
-
Избавление от дублирования кода. Раньше для работы с разными типами приходилось писать отдельные функции для каждого из них (например,
SortInts,SortStrings). Дженерики позволяют создать одну универсальную функцию. -
Типобезопасность без
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"}
Таким образом, дженерики делают код более чистым, переиспользуемым и безопасным, устраняя необходимость в бойлерплейте и небезопасных преобразованиях типов.
Ответ 18+ 🔞
А, дженерики в Go, блядь! Ну наконец-то, ёпта! Сидели мы тут, как дураки, с этими interface{}, которые нихуя не проверялись, пока код не запустишь и он тебе в сраку не влетит с паникой. А теперь — красота, ядрёна вошь!
Смотри, в чём была главная засада. Раньше, если тебе надо было отсортировать и числа, и строки, ты писал две отдельные функции, как последний идиот. SortInts, SortStrings — пиздец, одно название уже вызывало волнение ебать. Копипаста на ровном месте, чих-пых тебя в сраку!
А второй косяк — это вот этот ваш interface{}, который все так любят. Ну типа, "ой, я сейчас универсальную функцию напишу!" А на деле получается: суёшь туда что попало, компилятор молчит как партизан, а потом в рантайме — хрясь! — panic: interface conversion: interface {} is string, not int. И сидишь, чешешь репу: "Э, бошка, думай... где же я накосячил?"
И производительность, блядь, страдала! Потому что каждый раз значения в интерфейс упаковывались — это ж дополнительные телодвижения, нахер не нужные.
А теперь смотри, какая магия появилась в 1.18. Берём и пишем одну функцию на все случаи жизни:
// T и U — это типа как переменные, но для типов. Понял? Параметры типа, блядь.
// А `any` — это ограничение, по-русски: "бери любой тип, мне похуй".
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"}, всё типобезопасно!
Вот и вся история. Теперь код чище, переиспользуется на ура, и компилятор стоит на страже, как суровый охранник в клубе, не пуская всякую хуйню. Красота, ёпта!