Ситуация: тип объявляет соответствие протоколу только в extension. Почему generic-функция всё равно принимает его как параметр с ограничением этим протоколом?
Потому что соответствие протоколу является свойством типа, а не местом, где оно записано. extension лишь добавляет к уже объявленному типу конформность и её реализации; generic-код видит ту же самую конформность и может использовать соответствующий witness table.
Расположение объявления в основном теле типа или в extension обычно не меняет семантику соответствия. Оно влияет главным образом на организацию кода, область доступности и возможность выразить условную конформность.
Расширения в Swift позволяют разделять основное описание типа и дополнительные возможности: методы, вычисляемые свойства и соответствия протоколам. Это решает проблему перегруженных объявлений и позволяет добавлять поведение к типу без изменения его исходного тела.
Для протоколов такой подход особенно важен: соответствие можно разместить рядом с адаптацией типа к конкретному API или предметной области, сохраняя основное объявление более сфокусированным.
Разработчик может ошибочно считать, что generic-код видит только протоколы, явно указанные после имени типа. Из этого возникает неверный вывод: будто соответствие из extension действует только внутри этого расширения.
На самом деле generic-функция должна получить доказательство соответствия типа протоколу. Если соответствие доступно в текущем модуле и удовлетворяет ограничениям, компилятор передаёт в generic-код необходимые реализации требований протокола. Если соответствие условное, дополнительно должны быть доказаны его условия.
Swift использует номинальную типизацию протоколов: тип соответствует протоколу не потому, что случайно имеет подходящие методы, а потому, что это соответствие явно объявлено. Объявление в extension регистрирует ту же номинальную конформность, что и объявление в основном теле типа.
При проверке generic-вызова компилятор ищет конформность конкретного типа к требуемому протоколу. Затем он связывает требования протокола с их реализациями — собственными членами типа, реализациями из протокольных расширений или синтезированными реализациями. Место объявления extension не препятствует этому поиску.
В примере User соответствует CacheKey, хотя соответствие записано в extension. Тип K получает и связанный тип K.Value, и доступ к требованию value.
Важно отличать обычную и условную конформность. Обычная конформность действует для всех экземпляров типа, а условная — только при выполнении ограничений, например когда параметр generic-типа сам соответствует нужному протоколу. В последнем случае generic-код сможет использовать конформность только после доказательства этих ограничений.
Extension не создаёт новый тип и не изменяет идентичность существующего типа. Поэтому нельзя объявить две независимые конформности одного типа к одному протоколу, рассчитывая выбирать между ними в разных generic-вызовах.
В проекте есть модель User, используемая в нескольких подсистемах: в доменной логике, кеше и сетевом слое. Возможны два варианта: перечислить все соответствия протоколам в объявлении User либо вынести каждую адаптацию в отдельный extension.
Основное объявление делает зависимости типа заметными сразу, но быстро становится перегруженным и связывает модель с деталями разных подсистем. Extensions улучшают разделение ответственности, однако при большом количестве файлов соответствие может быть сложнее найти.
Практичным решением будет вынести конформность к CacheKey в extension рядом с кодом кеша. Generic-код при этом не теряет типобезопасность: функция по-прежнему получает полноценное доказательство соответствия, а associated type сохраняется. Следует лишь обеспечить подходящий уровень доступа для самого типа, протокола и необходимых членов.
Имеет ли значение порядок объявления типа и extension с конформностью?
Обычно нет: компилятор рассматривает объявления типа и его extensions как части единого описания типа в пределах доступного исходного кода. Generic-вызов проверяется по итоговой модели типов, а не только по тексту, расположенному выше вызова.
Однако порядок файлов и область видимости всё равно важны для доступности деклараций, условной компиляции и межмодульного использования. Нельзя полагаться на конформность, которая не попала в текущую сборку или недоступна из другого модуля.
Будет ли конформность из extension доступна пользователю другого модуля?
Да, если сам тип, протокол и конформность имеют достаточную видимость. Внутренняя конформность может использоваться внутри модуля, но не обязана предоставлять внешнему модулю доказательство, необходимое для вызова его generic-кода.
Поэтому публичный API должен иметь согласованные уровни доступа. Недостаточно сделать публичным только тип: внешнему коду также должна быть видна требуемая конформность и доступные реализации её требований.
Может ли extension с конформностью обращаться к закрытым членам исходного типа?
Да, если extension находится в том же исходном файле: private-члены доступны в рамках файла, содержащего их область видимости. Extension в другом файле такого доступа не получает, поэтому реализацию требований придётся строить на открытых или подходящих по уровню доступа членах.
Это влияет на организацию кода. Размещение конформности в отдельном файле улучшает разделение подсистем, но может потребовать изменить уровень доступа или добавить публичный интерфейс, через который extension реализует требования протокола.