Как вы уже наверняка знаете, proposal по дженерикам в Golang принят (официально это называется type parameters) и будет имплементирован в go 1.18. Бета будет доступна уже в конце этого года. А это значит, что пора разобраться, на чём в итоге остановились разработчики языка — ведь черновик type parameters постоянно менялся в течение последних лет. Технология новая, на практике толком никто не использовал. Поэтому если увидите какую-то неточность в статье, не стесняйтесь указать это в комментариях. Самостоятельно поиграться с дженериками можно здесь Итак, поехали. Несколько лет назад у нас в Каруне возникла необходимость перевести несколько сервисов с PHP на GO. Как сейчас помню, в первой же программе потребовалось проверить, существует ли строка в некотором слайсе строк. Беглый гуглёж показал, что встроенной функции, аналогичной пхпшной in_array() в языке нет. Поэтому пришлось написать свою, что-то типа такого: Но проблема в том, что когда надо поискать int в слайсе интов, функция получается абсолютно такой же, отличие только в сигнатуре. Написать универсальную функцию под все типы — задача не очень простая. Можно использовать reflect и interface{}, как в примере на stackoverflow, но это, понятное дело, выглядит не очень и подвержено ошибкам, не проверяемым в момент компиляции. Или же можно использовать кодогенерацию, что тоже в общем-то так себе, так как это лишний шаг при билде. Забегая вперёд, в go 1.18 это будет решаться так: Вопрос дискуссионный. Мнения разделились. Как известно, язык Go изначально был заточен под максимальную простоту, и обобщение типов может усложнить читабельность кода. Многие противопоставляют Go языку Java, традиционно наполненному обобщениями различного рода, и дженерики — это как первый шаг в эту сторону. Теряется этакая гошная "дубовость" (в хорошем смысле), прямолинейность. Тем более, что в обычном продуктовом коде люди годами обходятся и без этого. Порой проще скопипастить немного кода и не париться. Как говорили в одном докладе, копипаста — это "хорошее медитативное занятие". С другой стороны, если надо написать универсальную библиотеку для каких-то универсальных целей, то придётся использовать interface{} или кодогенерацию, а это тоже в общем-то читабельности и надёжности не добавляет. Также необходимо отметить, что разработчики языка сделали всё возможное, чтобы дженерики выглядели и использовались как можно проще. Намного проще, чем в других языках. Согласно результатам опроса 88% респондентов назвали отсутствие дженериков критической проблемой. 18% опрошенных сказали, что не используют Go именно из-за отсутствия этой функциональности (цитата: "18% of respondents are prevented from using Go because of a lack of generics"). Вот простейший пример. Функция, построчно печатающая элементы слайса любого типа. т.е. после имени функции в квадратных скобках описан некий идентификатор T (который дальше будет использован там, где вы бы раньше использовали обычный тип) и констрейнт (ограничение) для этого типа (констрейнт any означает, что в качестве T можно передать любой тип). Таких идентификаторов может быть несколько (через запятую); констрейнт для них указывать обязательно, это будет подробнее описано ниже. А вот так происходит вызов такой функции, уже с конкретным типом string: Т.е. синтаксически по сути мы передаём в функцию тип как обычный аргумент (параметр). Просто такие "аргументы" передаются и описываются в сигнатуре в отдельных квадратных скобках вместо круглых. Поэтому функциональность так и называется: type parameters. В некоторых случаях явным образом передавать тип не надо, компилятор его сможет вывести сам по переданным аргументам: У каждого параметра-типа обязательно указывается ограничение типа. , где MyConstraint — это интерфейс, который описывает, каким может быть тип. Этот интерфейс может быть обычным go-интерфейсом, описывающим требуемые методы. А может быть интерфейсом, перечисляющим полный список типов, для которых он может быть использован. Обратите внимание, что такой интерфейс с перечислением пока что можно использовать только в дженериках и нигде больше. В будущих версиях будут и другие применения. Есть встроенные в язык констрейнты, например any (синоним interface{}) и comparable (ограничивающий типы, для которых определены операторы сравнения). Также в стандартную библиотеку планируется добавить пакет constraints, где будут добавлены различные полезняшки. Например, constraints.Number (под это подходят любые типы а ля int, float32 и т.д. ) Помимо функций подобным образом можно работать и с описанием типа. Например, если вы пишете функции для умножения и сложения векторов, вам захочется иметь универсальный тип Vector При использовании такого типа нужно указать в квадратных скобках его конкретный уже тип: Вот более-менее полный пример функции сложения векторов поиграться с примером можно здесь: https://gotipplay.golang.org/p/UZ3GdScMwZC (в примере я не использовал встроенный интерфейс constraints.Number, так как на go2goplay.golang.org это почему-то не работает. Пришлось делать свой доморощенный interface Number) Обратите внимание на то, что здесь нельзя использовать констрейнт any, так как операция сложения определена далеко не для всех типов, и вы получите соответствующую ошибку "operator + not defined". В стандартную библиотеку планируется добавить несколько пакетов, таких как slices, maps, chans и т.д., которые будут предоставлять универсальные функции для работы со слайсами, каналами и т.д. Пакеты container/list, container/ring, sync и другие будут доработаны с точки зрения типобезопасности. Math получит новые универсальные функции для любых чисел (например Min и Max) Т.е. обобщенная функция будет компилироваться один раз и будет иметь некоторые потери в рантайме. В то же время обобщённые типы будут компилироваться для каждого варианта отдельно, что увеличит лишь время компиляции. Цитата: Как известно, java удаляет информацию о дженериках после компиляции. В golang это не так. Также, в Джаве реализована ковариантность и контрвариантность (List<? extends Number>, List<? super Number>), в Go всё намного проще: мы просто передаём тип как параметр, а тип ограничен интерфейсом. Кстати, многие спрашивают, почему нельзя было сделать стандартные для многих языков (включая Java) скобки <>, а ввели новый вариант в виде квадратных скобок? Дело в том, что парсер языка Go сделан максимально простым и быстрым — это ключевая особенность языка. В случае с треугольными скобками в некоторых ситуациях невозможно распарсить строку без знания информации о типах, например: можно трактовать как Типобезопасная функция, объединяющая два канала в один Делаем свой Set на основе map Пример использования Дальше — больше. Есть куча предложений, как улучшить дженерики: в частности автоматический вывод типов в различных ситуациях или упрощённая работа с zero value. Но это всё по сути ждёт, когда основная функциональность попадёт в язык. В общем, поживём — увидим. Осталось уже недолго. Go-тимлид, веду канал https://t.me/crossjoin