Go: Дженерики
Теория: Ограничения (constraints)
Когда мы пишем обобщённые функции в Go, важно управлять тем, какие типы можно использовать. Если не задать ограничение, параметр типа подходит для любого значения, но далеко не всегда это безопасно. Например, функция поиска минимального значения должна работать только с теми типами, для которых определены операции < и >. Для этого в Go существуют ограничения, которые указываются рядом с параметром типа.
Самое простое ограничение — any. Оно означает, что функция не накладывает никаких условий. Такой вариант подойдёт, например, для реверса среза:
Функция работает одинаково для []int, []string и любых других срезов.
Если нужно проверять равенство, используют встроенное ограничение comparable. Оно разрешает операции == и !=:
Здесь T обязан быть сравнимым. Попробовать вызвать функцию с несравнимым типом, например срезом, не получится — компилятор выдаст ошибку.
Для работы с упорядоченными данными удобно использовать constraints.Ordered из пакета golang.org/x/exp/constraints. Он включает числа и строки, для которых доступны операции < и >:
Такое ограничение позволяет одной функцией сравнивать и числа, и строки.
Если встроенных ограничений недостаточно, можно описать свои. Например, интерфейс для типов, у которых есть метод String() string:
Теперь вызвать функцию можно только с теми типами, которые реализуют метод String().
Ограничения можно комбинировать через встраивание. Допустим, нужно, чтобы тип одновременно был сравнимым и имел метод Name() string:
Функция с таким параметром типа сможет работать только с подходящими структурами:
Наконец, можно ограничивать параметр конкретными базовыми типами. Для этого в интерфейсе перечисляют набор через |:
Здесь Sum работает только с числами. Если попытаться вызвать её со строками или булевыми значениями, компилятор не даст собрать программу.
Ограничения позволяют точно задать правила для параметров типа. Они бывают встроенными (any, comparable, constraints.Ordered) и пользовательскими, могут требовать методы или конкретные наборы типов. Это делает обобщённые функции гибкими, но при этом безопасными: компилятор заранее проверяет, что выбранный тип соответствует условиям.



