Разработчик заменил обобщённое требование протокола методом для конкретного типа. Почему соответствие не засчитывается?
Соответствие не засчитывается, потому что обобщённое требование протокола обязано работать для любого типа, допустимого его параметром. Метод, принимающий только конкретный тип, поддерживает лишь один частный случай и не может быть его реализацией.
Swift проверяет соответствие по сигнатуре требования, а не по наличию похожей перегрузки.
Протоколы задают контракт, независимый от конкретного типа, а обобщённые требования позволяют выразить операции над множеством типов без перечисления каждого из них. Это нужно библиотекам, которым важно принимать новые типы без изменения самого протокола.
Такой контракт требует универсальной реализации: соответствующий тип должен предоставить witness — реализацию, совместимую со всем диапазоном входных типов требования.
Предположим, протокол требует метод, способный обработать значение любого типа. Если тип реализует только метод для Int, вызов с String, пользовательским типом или другим допустимым аргументом выполнить нельзя.
Если бы Swift принимал такую реализацию, generic-код мог бы успешно скомпилироваться на основании протокола, но затем не имел бы подходящего метода для части разрешённых аргументов. Поэтому частичная специализация не считается соответствием.
У обобщённого требования есть неявный смысл: реализация должна быть корректной для каждого допустимого значения параметра типа. Метод для конкретного типа не расширяет область допустимых аргументов, а сужает её.
Например:
IntFormatter не соответствует Formatter: его метод является обычным методом для Int, а не реализацией generic-требования. Для соответствия нужна обобщённая реализация с совместимой сигнатурой:
Наличие обеих перегрузок также не меняет правила: соответствие обеспечивает именно метод, совпадающий с требованием. Компилятор не объявляет частный метод универсальным автоматически и не выбирает его как witness только потому, что он подходит для одного вызова.
Проверяются не только типы параметров, но и их обобщённость, ограничения, внешние имена, возвращаемый тип и другие части сигнатуры. Более узкое ограничение generic-параметра также может нарушить соответствие, поскольку такая реализация поддерживает не все случаи, разрешённые исходным требованием.
В библиотеке протокол Encoder может требовать преобразование значения любого типа в диагностическое представление. Разработчик конкретного модуля реализует метод только для User, рассчитывая, что библиотека будет вызывать его лишь с пользователями. Однако библиотека видит только контракт протокола и вправе передать любой тип.
Вариант с методом только для User прост и типобезопасен внутри модуля, но не удовлетворяет контракту. Можно изменить протокол и сделать требование специализированным для User, однако это уменьшит переиспользуемость библиотеки. Можно добавить отдельную перегрузку для User, сохранив универсальный метод: это даст специализированное поведение при обычных вызовах, но соответствие протоколу всё равно будет обеспечивать generic-метод.
Обычно выбирают третий вариант: универсальный метод реализует обязательный контракт, а специализированная перегрузка используется как дополнительный API. Результат — корректное соответствие и возможность оптимизировать часто встречающийся тип без ослабления протокола.
Да, если generic-метод сам точно соответствует требованию протокола. Метод для конкретного типа остаётся обычной перегрузкой и не участвует в удовлетворении generic-требования. При разрешении вызова Swift может выбрать более подходящую перегрузку в статически известном контексте, но это не меняет witness, выбранный для протокола.
Обычно нет, если исходное требование допускает более широкий набор типов. Например, требование для любого T нельзя заменить методом только для T: Equatable, потому что реализация не покрывает типы, не соответствующие Equatable. Ограничение должно быть совместимо с контрактом, иначе часть разрешённых протоколом вызовов становится невозможной.
Нет. При установлении соответствия типа протоколу Swift фиксирует реализацию требования в таблице witness. Generic-код использует эту связанную реализацию; последующая специализация функции может быть оптимизирована компилятором, но не меняет правило соответствия и не превращает частную перегрузку в witness.