Сравнение: чем по смыслу для компилятора отличается псевдоним типа от нового именованного типа?
Псевдоним типа не создаёт новый тип: он обозначает уже существующий тип, поэтому значения остаются полностью совместимыми с исходным типом. Объявление нового именованного типа создаёт отдельный тип с той же базовой структурой, но с собственной идентичностью, поэтому между ним и исходным типом обычно требуется явное преобразование.
Иными словами, псевдоним помогает сохранить совместимость при переименовании или перемещении типов, а новый именованный тип используется для усиления типовой безопасности.
В Go псевдонимы типов появились как средство постепенного рефакторинга больших кодовых баз. Они позволяют изменить имя или место публичного типа, не заставляя весь существующий код немедленно переходить на новое обозначение.
До появления псевдонимов объявление через type воспринималось как создание нового именованного типа. Для задач вроде выделения идентификаторов, денежных величин или единиц измерения это полезно, но для переименования API могло создавать много несовместимых изменений.
Представим, что программа хранит идентификаторы пользователей как целые числа. Если использовать обычный int, компилятор не отличит идентификатор пользователя от любого другого целого числа, и ошибочное смешивание значений останется незамеченным.
Псевдоним сохраняет такую совместимость, но не защищает от смешивания. Новый именованный тип, напротив, обнаруживает ошибку на границе функции, однако требует явных преобразований при взаимодействии с библиотеками и API, принимающими исходный тип.
Псевдоним записывается концептуально как type Alias = Existing. Имя Alias и имя Existing обозначают один и тот же тип: совпадают идентичность типа, набор методов и правила присваивания.
Новое именованное объявление имеет смысл type New Existing. Тип New отличается от Existing, хотя его базовый, или underlying, тип тот же. Это различие учитывается компилятором при проверке присваиваний, аргументов функций и реализации интерфейсов.
Здесь LegacyID полностью эквивалентен int, поэтому его можно передать в save без преобразования. UserID является отдельным именованным типом, поэтому перед вызовом требуется явное преобразование int(user).
Новый тип может иметь собственные методы, если он объявлен в текущем пакете и удовлетворяет ограничениям для типа-получателя. Псевдоним отдельного набора методов не создаёт: методы относятся к исходному типу, который он обозначает.
Явное преобразование между типами с совместимым представлением обычно не меняет данные, но оно фиксирует намерение разработчика. Это не означает, что любые типы с одинаковым размером в памяти взаимозаменяемы: правила определяются системой типов Go, а не только физическим представлением.
У нового типа есть компромисс: он защищает от случайного смешивания значений, но добавляет преобразования на границах с базовыми типами, стандартными функциями и внешними библиотеками. Псевдоним удобнее для совместимости, но не предоставляет дополнительной типовой защиты.
В сервисе идентификаторы пользователей и заказов изначально были представлены как int. В одном обработчике идентификатор заказа случайно передали функции, ожидающей идентификатор пользователя; программа успешно скомпилировалась, а ошибка проявилась только при обращении к данным.
Рассматривались три варианта:
int: минимум изменений, но защита отсутствует;Выбрали новые именованные типы для доменных идентификаторов, а преобразования ограничили адаптерами на границах базы данных и HTTP-слоя. В результате часть кода стала немного подробнее, но класс ошибок со смешиванием идентификаторов был устранён до запуска программы.
Обычно нет, если оба типа являются именованными. Например, значение типа UserID нельзя без явного преобразования передать параметру типа int, даже если базовый тип UserID — int. Одинаковое внутреннее представление не отменяет различие идентичности типов.
Исключения связаны прежде всего с нетипизированными константами и специальными правилами присваиваемости Go. На них не следует рассчитывать для переменных и аргументов обычных функций: для явной границы используйте преобразование.
Нет. Новый тип получает тот же базовый тип, но не копирует его методы. Поэтому методы, определённые для исходного именованного типа, не становятся автоматически методами нового типа.
Для нового типа можно определить собственные методы в том же пакете. Это позволяет добавить доменное поведение, например валидацию идентификатора, но одновременно означает, что реализацию методов придётся поддерживать отдельно.
Нет. Псевдоним сохраняет идентичность исходного типа. Значения, параметры функций, методы и проверки интерфейсов продолжают относиться к одному типу, независимо от того, какое имя использовано в конкретном месте.
Именно поэтому псевдоним подходит для совместимого переименования. Если целью является запрет случайного смешивания значений, псевдоним не подходит — нужен новый именованный тип или другая отдельная структура данных.