В обобщённой функции параметр типа используют как ключ map. Какое ограничение делает такую функцию допустимой?
Параметр типа должен быть ограничен интерфейсом comparable. Это гарантирует, что для всех допустимых подстановок типа операция помещения значения в map[K]V разрешена компилятором.
Однако comparable не означает полной гарантии отсутствия паники во время выполнения: если в качестве K фактически используется интерфейсное значение с нехешируемым динамическим типом, операция с map может завершиться паникой.
С появлением generics стало необходимо описывать не только набор методов типа, но и допустимые операции над параметром типа. Ограничение comparable решает задачу статической проверки операций сравнения и использования параметра типа в качестве ключа карты.
Без такого ограничения компилятор не может предположить, что произвольный тип поддерживает сравнение и хеширование: срезы, map и функции, например, нельзя сравнивать между собой и использовать как ключи map.
Обычная map требует, чтобы её ключ имел сравнимый тип. Если обобщённая функция принимает произвольный K, компилятор не может безопасно разрешить обращение к map[K]V, поскольку среди возможных подстановок могут оказаться нехешируемые типы.
Попытка обойти ограничение через any не решает проблему. Такой параметр может принимать срезы или map, поэтому использование K как ключа должно быть отклонено ещё на этапе компиляции.
Ограничение K comparable сужает множество допустимых типов до тех, для которых определены сравнение и хеширование. Поэтому компилятор разрешает объявление map[K]V, чтение, запись и удаление элементов.
Ограничение проверяется для каждой конкретной подстановки параметра типа. int, string, указатели, каналы, интерфейсы, а также структуры и массивы из сравнимых компонентов подходят. Срезы, map и функции не подходят.
Интерфейсный тип требует отдельной осторожности. Сам интерфейс можно использовать как тип ключа, но при операции с конкретным ключом Go проверяет его динамическое значение. Если оно содержит срез, map или функцию, программа может получить панику из-за попытки хеширования нехешируемого значения.
Поэтому comparable прежде всего даёт статическую совместимость с map, а не абсолютную гарантию безопасности любого вызова. Для API, где ключи приходят из ненадёжного источника, иногда лучше принимать заранее нормализованный тип, например строку, либо явно проверять структуру входных данных.
Нужно реализовать обобщённый кэш. Вариант с параметром K any не компилируется: функция не может объявить map с произвольным ключом. Вариант с K comparable компилируется и сохраняет универсальность для чисел, строк, указателей и сравнимых структур.
Можно было бы принимать ключ как строку и сериализовать исходное значение. Такой подход предотвращает проблемы с нехешируемыми типами, но требует определённого формата сериализации, добавляет накладные расходы и может приводить к коллизиям или неоднозначному представлению.
Практичный выбор — K comparable, если набор ключей контролируется кодом и состоит из обычных сравнимых типов. Если ключи поступают через интерфейсы или внешние данные, безопаснее сначала преобразовать их в явно выбранное каноническое представление, а уже затем использовать в map.
any, если фактический тип при вызове окажется int?Нет. Обобщённая функция проверяется с учётом всего множества типов, разрешённых ограничением, а не только предполагаемого типа в одном вызове. Поскольку any допускает нехешируемые типы, компилятор не разрешит использовать параметр K any как ключ map.
comparable, что обращение к map никогда не завершится паникой?Нет, если допустимой подстановкой является интерфейсный тип. Интерфейс может содержать динамическое значение с нехешируемым типом, например срез. Поэтому ограничение обеспечивает корректность операции на уровне типов, но для интерфейсных значений остаётся риск ошибки времени выполнения.
Структура сравнима, если сравнимы все её поля, включая вложенные поля. Сравнение и хеширование выполняются с учётом каждого поля. Если хотя бы одно поле имеет тип срез, map или функции, сама структура также не может использоваться как ключ; если поля содержат только сравнимые типы, она подходит.