Допустим, нужно хранить в одной коллекции разные реализации протокола с associated type. Как type erasure решает эту задачу?
Type erasure скрывает конкретный тип реализации протокола за отдельным типом-обёрткой. Обёртка сохраняет нужное поведение, обычно через замыкание или внутренний адаптер, поэтому разные реализации можно хранить вместе. При этом информация о конкретном associated type стирается или заменяется заранее выбранным общим типом.
Протоколы с associated type описывают семейство типов: каждая реализация может выбрать собственный связанный тип. Это удобно для статически типизированного generic-кода, но затрудняет создание однородной коллекции из реализаций с разными связанными типами.
Type erasure сформировался как практический паттерн для отделения публичного интерфейса от конкретного типа реализации. В стандартной библиотеке Swift этот подход используется, например, в обёртках вроде AnyHashable и других type-erased типов.
Пусть один запрос возвращает число, другой — строку. Их конкретные типы соответствуют одному протоколу, но значения их associated type различаются.
Если сохранить такие объекты напрямую, коллекция должна иметь один статически известный тип элемента. Простое сокрытие конкретного типа не решает проблему: вызывающий код всё равно должен понимать, какой тип возвращает операция протокола.
Неверное решение — притвориться, что все результаты имеют один тип без явного правила преобразования. Это либо не скомпилируется, либо приведёт к потере статической типизации и необходимости небезопасных приведений типов.
Type eraser принимает любую реализацию протокола как generic-параметр, а затем сохраняет внутри адаптированное поведение. Внешний тип больше не сообщает конкретную реализацию и может использоваться как единый тип элемента коллекции.
Инициализатор видит конкретный R и знает его Response, поэтому может создать замыкание, вызывающее send(). После этого наружу предоставляется только AnyRequest, а конкретный Response заменяется на Any.
Главный компромисс — потеря статической информации. Код с AnyRequest не может безопасно считать, что результат всегда является Int; для дальнейшей обработки потребуются проверка типа, общий протокол результата или другой заранее выбранный формат.
Если все реализации должны возвращать один тип, лучше сделать type eraser параметризованным этим типом, например AnyRequest<CommonResponse>. Если набор вариантов известен заранее, часто безопаснее использовать перечисление. Если типы должны оставаться полностью различными, можно разделить операции по специализированным коллекциям вместо стирания типов.
В приложении есть слой сетевых запросов. Одни запросы возвращают модели, другие — метаданные, а диспетчер должен хранить их в очереди до выполнения.
Рассматривались три варианта. Использование протокола напрямую не даёт единого типа коллекции с общим способом извлечения результата. Использование Any решает проблему хранения, но переносит всю проверку типов в вызывающий код. Создание отдельного перечисления сохраняет типобезопасность, однако требует изменять перечисление при добавлении каждого нового вида запроса.
Выбран AnyRequest с явным стиранием результата, потому что очередь отвечает только за запуск запросов и логирование результата. Для бизнес-логики результаты сразу преобразуются в общий тип события, поэтому дальнейшее использование Any ограничено границей инфраструктурного слоя.
Нет. Обычно стирается не только имя реализации, но и информация, связывающая протокол с конкретным associated type. Обёртка должна заранее решить, что предоставить наружу: Any, общий протокол, фиксированный тип результата или ограниченный набор операций.
Замыкание создаётся в контексте, где конкретный тип реализации ещё известен. Оно может захватить объект и вызвать его метод, а наружу предоставить унифицированную сигнатуру. Такой подход обходится без необходимости обращаться к неизвестному вызывающему коду через конкретный associated type.
Нет. Если функция может оставаться generic, сохранение конкретного типа обычно даёт лучшую статическую проверку, оптимизацию и доступ к associated type. Type erasure оправдан, когда нужна гетерогенная коллекция, стабильный публичный API или сокрытие деталей реализации; его цена — дополнительные косвенные вызовы, возможные выделения памяти и потеря части типовой информации.