При передаче значения any-протокола в generic-функцию каким образом Swift связывает его скрытый конкретный тип с generic-параметром?
Swift временно открывает existential: извлекает из значения any P скрытый конкретный тип и связывает его с generic-параметром функции. Внутри конкретного вызова этот параметр рассматривается как один определённый тип, но сам тип остаётся неизвестным вызывающему коду.
Связанные с ним associated types сохраняют связь с этим скрытым типом. Однако она не может произвольно выйти наружу: результат или другая операция не должны требовать от вызывающего кода назвать неизвестный конкретный тип.
Протоколы с associatedtype долгое время было нельзя напрямую использовать как обычные existential-значения для generic-кода: existential стирает конкретный тип, а associated type зависит именно от него. При этом хранить такие значения в виде any P всё равно необходимо для гетерогенных коллекций и границ API.
Механизм implicitly opened existentials позволил передавать existential-значения в generic-функции, временно сохраняя их конкретный тип внутри вызова. Это уменьшает необходимость ручного type erasure, но не отменяет ограничений на утечку неизвестного типа наружу.
Пусть протокол описывает контейнер с неизвестным типом содержимого. После преобразования конкретного контейнера в any Container вызывающий код знает только, что значение соответствует протоколу, но не знает точный Element.
Generic-функции могут безопасно работать с одним открытым existential: читать его требования, вызывать методы и использовать associated type внутри тела. Но два existential-аргумента не считаются автоматически контейнерами одного типа, даже если фактически в них переданы одинаковые значения.
При вызове generic-функции Swift создаёт скрытую связь вида: «этот конкретный existential содержит тип τ, поэтому в данном вызове T == τ». Тип τ недоступен по имени, но компилятор использует его для проверки требований протокола и связанных associated types.
В describe C — это не сам тип any Container, а временно открытый конкретный тип, скрытый внутри erased. Поэтому Swift знает, что value.element имеет C.Element, хотя вызывающий код не знает, чем этот тип является.
Открытие выполняется для конкретного использования и не превращает any Container обратно в именованный тип. Нельзя безопасно объявить внешний результат как конкретный associated type этого existential: разные значения any Container могут иметь разные Element.
По той же причине generic-функция с двумя параметрами одного типа обычно требует, чтобы оба аргумента были одним и тем же generic-типом. Два независимых any Container могут быть открыты как разные скрытые типы τ₁ и τ₂; совпадение их associated types компилятор не предполагает.
Практический компромисс таков: existential удобен для хранения и передачи неизвестных реализаций, а generic-параметр сохраняет точные связи типов внутри операции. Если связь должна пережить границу вызова, её нужно выразить явно — например, передать один контейнер, использовать type erasure с заранее выбранным erased-типом или принять конкретный generic-тип на более высоком уровне.
В модуле обработки данных хранились разные реализации Container в одной коллекции [any Container]. Для каждой отдельной реализации требовалось получить и отформатировать element, но нельзя было объединять элементы разных контейнеров в одну типобезопасную последовательность без дополнительного соглашения об их типе.
Рассматривались два варианта. Полностью перейти на generics было бы типобезопасно, но потребовало бы сохранить один конкретный тип контейнера на всём пути вызовов. Сделать собственный type erasure с Any было проще для хранения, но пришлось бы отказаться от статической проверки типа элемента.
Выбран комбинированный подход: коллекция использует any Container, а операции над одним элементом передают его в generic-функцию. Это сохраняет типобезопасность внутри операции и одновременно позволяет хранить разные реализации; объединение элементов допускается только после явного выбора общего erased-типа.
1. Почему два параметра any-протокола не считаются параметрами одного скрытого типа?
Каждое existential-значение может содержать собственную реализацию. Поэтому при передаче двух значений Swift должен рассматривать их как потенциально разные скрытые типы τ₁ и τ₂. Даже если в конкретном запуске оба значения содержат одну и ту же реализацию, это не является гарантией типа, которую можно использовать для проверки generic-ограничений.
2. Может ли generic-функция вернуть associated type открытого existential?
Она может использовать этот associated type внутри тела, но результат не должен требовать от вызывающего кода назвать неизвестный тип. Обычно безопасны результаты, которые сразу стираются до известного типа, например Any, String или другой заранее заданный протокол без необходимости сохранить конкретную связь. Если результат должен иметь именно C.Element, вызывающему коду нужен доступный generic-контекст с этим C, а не только независимое any Container.
3. Чем открытие any P отличается от some P?
any P означает existential: конкретный тип скрыт внутри значения и может отличаться у разных экземпляров. Его можно использовать для хранения разных реализаций.
some P скрывает конкретный тип, выбранный самой функцией или свойством, но сохраняет его как одну фиксированную реализацию для соответствующего объявления. Поэтому some P обычно лучше сохраняет статическую связь типов, а any P лучше подходит для динамического хранения; неявное открытие existential лишь временно использует эту связь внутри generic-вызова и не меняет природу значения.