Что такое existential container в Swift
Что проверяют
Знаете ли вы, как значение протокольного типа устроено в памяти, и понимаете ли, почему any обходится дороже дженерика. Вопрос на глубину: по нему видно, кто читал про представление типов, а кто знает только слово «протокол».
Как отвечать
Когда переменная объявлена протокольным типом, конкретный тип на этапе компиляции неизвестен, а хранить значение нужно в фиксированном размере. Компилятор заворачивает его в existential container: буфер под само значение, указатель на метаданные типа и указатели на таблицы свидетелей для каждого требуемого протокола.
Буфер небольшой, порядка нескольких машинных слов. Значение, которое в него помещается, лежит прямо там; значение крупнее размещается в куче, а в буфере остаётся ссылка. Отсюда практический вывод: большая структура за протокольным типом даёт скрытое выделение памяти и подсчёт ссылок на каждое присваивание. Вызов метода идёт через таблицу свидетелей, то есть динамически, и обычно не встраивается.
Дальше покажите развилку:
func draw(_ shape: any Shape) // коробка, вызов через таблицу
func draw<S: Shape>(_ shape: S) // тип известен, вызов прямой
func make() -> some Shape // один конкретный тип, коробки нетКлючевое слово any в современном Swift как раз и делает коробку явной в коде. Массив разнородных элементов без неё не собрать, поэтому вывод не «избегать», а «не платить там, где тип и так один».
Где спотыкаются
Отвечают про полиморфизм вообще и не говорят ни слова про представление в памяти. Вопрос именно про контейнер, а рассказ о массиве фигур отвечает на соседний вопрос.
Считают any и some синонимами. Первое хранит любой соответствующий тип и стирает его, второе фиксирует один конкретный тип, известный компилятору, и коробку не создаёт вовсе.
Не связывают размер значения со скоростью. Протокольный тип поверх крупной структуры в горячем цикле это неочевидные выделения в куче, и увидеть их можно только в профилировщике, а не глазами по коду.