Ситуация: generic-тип должен соответствовать протоколу, если его параметр удовлетворяет хотя бы одному из двух протоколов. Как выразить такое условие в Swift?
Непосредственно выразить условие «соответствует протоколу A или протоколу B» в generic-ограничениях Swift нельзя. Ограничения поддерживают конъюнкцию — одновременное выполнение условий, — но не дизъюнкцию.
Обычно задачу решают отдельными перегрузками, адаптерами, type erasure или введением общего протокола, если допустимо изменить модель типов.
Generics и conditional conformance проектировались вокруг проверяемых на этапе компиляции ограничений: тип либо удовлетворяет набору требований, либо нет. Такой подход позволяет статически выбирать реализации и проверять доступность операций без runtime-проверок.
Обобщённого union-ограничения с логическим «или» в Swift нет. Это осложнило бы выбор соответствия, разрешение перегрузок и определение единого набора доступных требований, особенно когда тип удовлетворяет обоим альтернативным условиям.
Предположим, контейнер должен получить соответствие Storable, если его элемент соответствует LocalStorable или RemoteStorable. Запись одного условного соответствия с таким условием невозможна: where объединяет ограничения через логическое «и», а не «или».
Два отдельных объявления соответствия тоже не являются универсальным решением. Если один конкретный тип удовлетворяет обоим условиям, возникают два потенциальных пути к одному и тому же соответствию, что нарушает однозначность модели соответствий.
Для разных операций можно объявить отдельные generic-перегрузки:
Такой код разрешает вызов для типа, удовлетворяющего одному протоколу. Но для типа, удовлетворяющего обоим, вызов становится неоднозначным: Swift не обязан предпочитать одну из равноправных перегрузок.
Если требуется единое соответствие контейнера, практичнее создать адаптер, который явно представляет альтернативный способ хранения. Другой вариант — использовать общий протокол-абстракцию, но он выражает уже не «A или B», а единый набор требований, которому типы должны соответствовать.
Type erasure подходит, когда решение можно перенести на runtime: например, сохранить замыкание store внутри стирающего типа. Цена такого подхода — потеря части статической информации, возможные runtime-ошибки и дополнительная косвенность.
Важно отличать это от ограничения с двумя протоколами одновременно. Запись вроде T: A & B означает обязательное соответствие обоим протоколам и не моделирует альтернативу.
В библиотеке есть контейнер документов. Локальные документы сохраняются в файл, а удалённые — через сетевой клиент. Разработчик пытается сделать контейнер соответствующим Storable при соответствии элемента одному из двух протоколов.
Рассматривались варианты:
Выбранными были адаптеры для локального и удалённого хранения. Это сохранило предсказуемое поведение, позволило использовать разные реализации транспорта и не создало неоднозначного соответствия.
1. Вопрос: Можно ли заменить условие «A или B» протоколом-композицией A & B?
Нет. Композиция A & B требует, чтобы тип соответствовал обоим протоколам одновременно. Она усиливает ограничение, тогда как исходная задача требует принять тип, удовлетворяющий хотя бы одному из вариантов.
2. Вопрос: Что произойдёт с перегрузками, если конкретный тип соответствует обоим альтернативным протоколам?
Вызов может стать неоднозначным, потому что обе перегрузки одинаково применимы. Swift не использует сам факт соответствия двум протоколам как основание автоматически выбрать одну реализацию.
3. Вопрос: Почему type erasure не является полноценным аналогом union-ограничения?
Type erasure скрывает конкретный тип за общей оболочкой и обычно сохраняет нужное поведение через замыкания или таблицы операций. Однако компилятор больше не видит исходное альтернативное ограничение как статическое свойство generic-параметра: часть проверок и выбор реализации перемещаются на этап выполнения.