Вызов метода протокола с аргументом по умолчанию из extension: от чего зависит, будет ли вызов без аргумента допустимым?
Вызов без аргумента зависит от статического типа значения и от того, видит ли компилятор декларацию с аргументом по умолчанию. Значение конкретного типа может использовать default argument из extension протокола, но значение типа any P и generic-параметр T: P видят только требования протокола. Если в требовании аргумент обязательный, вызов без него для existential- или generic-значения недопустим.
Расширения протоколов в Swift позволяют добавлять общую реализацию требований и дополнительные методы без изменения каждого соответствующего типа. Это уменьшает дублирование и поддерживает стиль программирования через протоколы.
При этом аргументы по умолчанию — не часть динамического dispatch и не характеристика witness-таблицы соответствия. Компилятор подставляет такое значение на месте вызова, поэтому ему нужна подходящая декларация, доступная через статический тип выражения.
Рассмотрим протокол Logger с методом, принимающим обязательное сообщение. Его extension добавляет реализацию и значение по умолчанию для этого параметра.
Ошибка возникает не потому, что у фактического объекта нет реализации метода. Реализация есть, но статические типы any Logger и T предоставляют вызывающему коду только контракт протокола, где аргумент обязателен.
Для ConsoleLogger() компилятор рассматривает члены, доступные через конкретный статический тип. Он находит метод из Logger-extension с default argument, подставляет строку "empty", а затем вызывает реализацию, предоставленную этим extension.
Для any Logger набор доступных членов определяется existential-интерфейсом. В его интерфейсе присутствует требование log(_:) без значения по умолчанию. Наличие default argument в расширении не изменяет сигнатуру требования и не добавляет это значение в existential-интерфейс.
Generic-код работает аналогично: ограничение T: Logger гарантирует наличие требования протокола, но не даёт телу функции произвольно использовать дополнительные возможности конкретного типа или его extension. Поэтому logger.log("message") допустим, а logger.log() — нет.
Важно отличать default argument от default implementation. Default implementation определяет, какую реализацию требования можно использовать при формировании соответствия. Default argument лишь позволяет опустить аргумент в конкретном месте вызова. Он не становится частью протокольного требования автоматически.
Практическое правило: если вызов без аргумента должен работать через existential и generic-параметр, объявляйте значение по умолчанию в публичном интерфейсе, доступном этим формам вызова, либо добавляйте отдельный метод без параметров как требование протокола. Нельзя рассчитывать, что default argument из extension сохранит эту возможность после потери конкретного статического типа.
В библиотеке есть протокол логирования, а большинство вызовов должны использовать сообщение по умолчанию. Разработчик размещает default argument только в extension, потому что конкретные типы получают готовую реализацию. В тестах с конкретным ConsoleLogger всё работает, но слой приложения хранит логгер как any Logger, и вызов без аргумента перестаёт компилироваться.
Вариант с default argument только в extension удобен для конкретных типов и не меняет минимальный контракт протокола, но создаёт различие между concrete-, existential- и generic-вызовами. Добавление отдельного требования без параметров делает контракт явным и доступным во всех контекстах, однако расширяет протокол и увеличивает обязательную поверхность API.
Оптимальное решение зависит от намерения. Если отсутствие сообщения — часть абстракции логгера, следует объявить отдельное требование вроде logDefault() и дать ему default implementation. Если это лишь удобство конкретного API, default argument в extension допустим, но его нельзя обещать пользователям any Logger или generic-функций.
Нет. Соответствие проверяет наличие метода с сигнатурой требования. Значение по умолчанию относится к декларации, через которую выполняется вызов, а не к witness-реализации. Поэтому оно не влияет на сам факт соответствия типа протоколу.
Нет. Generic-функция компилируется относительно ограничений T: P, а не относительно каждого будущего конкретного типа. Внутри неё доступны только гарантированные требования протокола; динамический выбор witness-реализации не расширяет набор допустимых форм вызова и не добавляет аргументы, которые компилятор не видит статически.
Само требование всё равно описывает метод с параметром, а не отдельный метод без параметра. Вызов через протокол будет зависеть от того, допускает ли декларация требования default argument и видит ли её компилятор в соответствующем контексте. Наиболее надёжный способ сделать нулевой аргумент частью полиморфного контракта — объявить отдельное требование без параметров и предоставить ему реализацию в extension.