На границе функций переменная одного интерфейсного типа присваивается другому, более узкому интерфейсу. Почему компилятор проверяет это присваивание по статическому типу исходной переменной, а не по её фактическому динамическому значению?
Компилятор проверяет статический тип исходного выражения: его методический набор должен содержать все методы целевого интерфейса. Динамическое значение может реализовывать целевой интерфейс, но обычное присваивание интерфейсов этого не выясняет; для такой проверки во время выполнения нужен type assertion или type switch.
Неявная реализация интерфейсов в Go позволяет функциям зависеть от поведения, а не от конкретных типов. Статическая проверка присваиваний сохраняет преимущества этой модели: ошибки обнаруживаются при компиляции, а вызов через интерфейс не требует скрытой проверки совместимости в каждой точке передачи значения.
Интерфейсная переменная содержит динамический тип и динамическое значение, но её статический тип описывает только гарантированный набор методов. Если разрешить присваивание на основании текущего динамического значения, корректность кода зависела бы от конкретного объекта во время выполнения, хотя функция обязана быть безопасной для любого значения допустимого статического типа.
Например, значение типа Читающий может в одном вызове содержать объект, реализующий Закрывающий, а в другом — нет. Поэтому компилятор не может считать переменную Читающий пригодной для передачи туда, где требуется Закрывающий.
При присваивании интерфейсного значения целевому интерфейсу Go проверяет методический набор статического типа источника. Если исходный интерфейс объявляет все методы целевого, присваивание допустимо. Если исходный интерфейс методов не гарантирует, присваивание запрещается, даже когда фактический динамический тип конкретного значения эти методы имеет.
Вызов use(File{}) допустим: File реализует Reader. Но внутри функции переменная r имеет статический тип Reader, который не гарантирует наличие Close. Утверждение r.(Closer) проверяет динамический тип во время выполнения и в данном примере успешно.
Если исходный интерфейс является расширением целевого, присваивание разрешено: его методический набор уже содержит требуемые методы. Важно отличать это от преобразования конкретного типа и от type assertion: присваивание интерфейсов опирается на статическую информацию, а assertion проверяет динамическое значение.
Допустим, общий слой приложения принимает Reader, но некоторые реализации желательно закрывать после обработки. Есть два варианта.
Первый — изменить контракт функции на составной интерфейс, требующий и чтение, и закрытие. Это безопасно и явно, но исключает реализации, которые умеют только читать, поэтому снижает универсальность API.
Второй — оставить параметр типа Reader и опционально проверить поддержку Closer через assertion. Такой вариант сохраняет совместимость с большим числом реализаций, но закрытие становится необязательным; код обязан корректно обработать неуспешную проверку.
Если закрытие действительно является обязательным условием, выбранным решением должен быть составной интерфейс на границе функции. Если это дополнительная возможность, предпочтительнее assertion с явной политикой обработки результата.
Нет. При обычном присваивании компилятор не анализирует конкретное динамическое значение. Он видит только статический тип переменной и разрешает операцию лишь тогда, когда гарантии этого типа достаточны. Для проверки фактического типа применяется type assertion или type switch.
Да. Если исходный интерфейс содержит все методы целевого, он удовлетворяет целевому интерфейсу. Дополнительные методы не мешают: целевой интерфейс требует только свой контракт, а не точное совпадение методического набора.
any нельзя напрямую присвоить интерфейсу с методами?any — это псевдоним пустого интерфейса, его методический набор пуст. Он не даёт статической гарантии ни одного метода, поэтому прямое присваивание, например, интерфейсу с методом Read, запрещено. Нужно выполнить assertion к требуемому интерфейсу и обработать возможный отказ; это переносит проверку совместимости на время выполнения.