От чего зависит, можно ли присвоить значение одного типа переменной другого типа без явного преобразования в Go?
Присваивание разрешено не просто при совпадении представления значений, а по правилам присваиваемости Go. Обычно достаточно идентичных типов, совместимых базовых типов, соответствия интерфейсу, присваивания нетипизированной константы представимого значения или передачи nil типу, допускающему nil.
Два разных именованных типа с одинаковым базовым типом обычно нельзя присвоить друг другу напрямую: между ними требуется явное преобразование.
Статическая типизация должна одновременно предотвращать случайное смешивание смыслов данных и не заставлять разработчика писать преобразования для каждого простого значения. Поэтому Go разделяет именованные типы, которые выражают отдельные концепции, и совместимость их базовых типов в ограниченных случаях.
Такой подход позволяет, например, использовать целочисленный литерал там, где ожидается пользовательский тип, но не позволяет незаметно передать один доменный идентификатор вместо другого.
Представим два типа UserID и OrderID, оба основанные на int. Их машинное представление одинаково, но смысл различается. Если бы Go разрешал любое присваивание между ними, ошибка компиляции не защищала бы от передачи идентификатора заказа в функцию, ожидающую идентификатор пользователя.
Обратная крайность тоже неудобна: обычное значение типа int, нетипизированный литерал или значение, реализующее интерфейс, должны использоваться без лишнего шаблонного кода. Поэтому правила учитывают не только базовый тип, но и именованность, интерфейсы и представимость констант.
Ключевые случаи присваивания такие:
nil присваивается указателю, срезу, map, каналу, функции или интерфейсу;Например:
Alias не создаёт новый тип: это другое имя для int. Поэтому b допустима. UserID и OrderID — разные именованные типы; несмотря на одинаковый базовый тип, присваивание u в o запрещено. Переменная a имеет именованный тип int, но UserID также именован, поэтому в общем случае требуется явное преобразование; строка var c UserID = a должна быть ошибкой компиляции. В отличие от неё литерал 10 нетипизирован и может получить тип UserID, если значение ему представимо.
Важно отличать нетипизированную константу от типизированной. Константа, объявленная с явным типом int, уже имеет тип int и не получает преимуществ нетипизированного значения при присваивании именованному целевому типу.
Для структур базовые типы считаются одинаковыми только при полном совпадении структуры, включая имена, типы, порядок и теги полей. Для интерфейса проверяется не базовый тип, а наличие требуемых методов в его методном наборе.
Явное преобразование меняет тип значения по правилам преобразований Go, но не делает два именованных типа взаимозаменяемыми во всех дальнейших операциях. После преобразования результат уже имеет целевой тип.
В API есть функции, принимающие UserID и OrderID, оба определённые на основе int. Разработчик может выбрать псевдонимы типов ради совместимости со старым кодом, но тогда компилятор не будет различать эти значения по смыслу. Это удобно при миграции, однако снижает защиту от ошибок.
Второй вариант — использовать два новых именованных типа. Он требует явных преобразований на границах слоёв, зато случайная подмена идентификаторов становится ошибкой компиляции. Третий вариант — оставить один общий тип int, что проще, но полностью переносит контроль корректности на разработчика и тесты.
Для публичного доменного API обычно выбирают отдельные именованные типы. Небольшая стоимость явных преобразований оправдана тем, что компилятор предотвращает целый класс ошибок ещё до запуска программы.
Нет, если оба типа именованные. Например, UserID и OrderID, основанные на int, остаются разными типами. Нужно явное преобразование, потому что одинаковое представление не означает одинаковый смысл.
Нетипизированная константа не закреплена за конкретным базовым типом до момента использования. Если её значение представимо целевым типом, контекст присваивания задаёт этот тип. Поэтому целочисленный литерал может сразу стать UserID, а переменная типа int — нет без явного преобразования.
Для присваивания конкретного значения интерфейсу — да, если тип значения реализует интерфейс, то есть имеет все требуемые методы с точно совпадающими именами, параметрами и результатами. Базовый тип и совпадение полей здесь не важны. При этом для типа-указателя и типа-значения методные наборы могут различаться, поэтому они способны по-разному удовлетворять одному интерфейсу.