В чём причина того, что ограничение по базовому типу int принимает именованный тип, основанный на int?
Ограничение с тильдой перед типом описывает не только сам тип int, а все типы, чей базовый тип — int. Поэтому именованный тип, основанный на int, входит в такое множество типов, хотя остаётся отдельным типом и автоматически в int не преобразуется.
С появлением обобщений в Go интерфейсы получили возможность описывать не только набор методов, но и множество допустимых типов. Это позволило задавать ограничения для параметров типа, например разрешать операции над всеми целочисленными типами.
Запись с тильдой появилась для описания семейств пользовательских типов с одинаковым базовым типом. Без неё ограничение перечисляло бы только точные типы и было бы менее пригодно для обобщённых функций.
Именованный тип на основе int не является синонимом int: между ними сохраняется различие типов. Однако такой тип должен использоваться в обобщённой функции, рассчитанной на целочисленные значения, например для вычисления суммы или удвоения значения.
Неверное понимание механизма приводит к двум ошибкам: разработчик либо необоснованно отклоняет пользовательский тип, либо считает, что ограничение автоматически выполняет преобразование значения в int. На практике это может привести к неверным ограничениям API и неожиданным ошибкам присваивания.
Ограничение ~int включает все типы, для которых базовым типом является int. В это множество входит сам int и любой определённый пользователем тип, объявленный на его основе.
Тильда влияет на состав множества типов, но не меняет правила типизации. Именованный тип сохраняет свою идентичность, поэтому значение такого типа нельзя присвоить переменной типа int без явного преобразования.
Здесь UserID удовлетворяет ограничению ~int, а функция сохраняет параметр типа UserID и возвращает значение того же типа. При этом явное преобразование int(id) всё равно требуется для присваивания значения переменной типа int.
Ограничение int без тильды означало бы точное соответствие типу int и не включало бы UserID. Кроме того, ~int само по себе не добавляет методы: доступные операции и методы определяются всем ограничением целиком.
Компромисс состоит в широте ограничения. ~int удобно для переиспользуемых обобщённых функций, но принимает больше типов, чем может ожидать конкретный API. Если функция должна работать только с точным int, следует использовать ограничение без тильды или обычный параметр типа int.
В библиотеке идентификаторы представлены пользовательскими типами UserID и OrderID, оба основаны на int. Нужно реализовать обобщённую функцию для арифметической операции, не теряя исходный тип результата.
Вариант с параметром типа int потребовал бы предварительного преобразования каждого идентификатора в int. Это упрощает ограничение, но теряет типовую информацию и вынуждает отдельно восстанавливать тип результата.
Вариант с ограничением ~int принимает оба пользовательских типа и возвращает тот же тип, что и аргумент. Он выбран потому, что сохраняет типобезопасность и не требует дублирования функций; риск чрезмерно широкого приёма типов в данном случае контролируется контрактом библиотеки.
~int типы, у которых базовый тип определён косвенно через другой именованный тип?Да, если после определения базового типа по правилам Go результатом является int. Важно отличать базовый тип от имени, использованного в объявлении: цепочка определённых типов в итоге может приводить к одному базовому типу.
~int присвоить параметр типа переменной типа int без преобразования?Нет. Параметр типа не обязан быть именно int: он может оказаться UserID или другим допустимым типом. Ограничение гарантирует принадлежность типу множеству и допустимость определённых операций, но не делает значения взаимозаменяемыми с конкретным типом int.
~int разрешает арифметику, но не гарантирует наличие пользовательских методов?Арифметические операции доступны, потому что все типы в множестве ~int поддерживают операции, определённые для int. Методы же не выводятся из базового типа: пользовательский тип может иметь собственные методы, а само ограничение ~int не требует ни одного метода. Чтобы обобщённая функция могла вызывать метод, его нужно явно включить в интерфейсное ограничение.