Зачем рекурсивному перечислению Swift требуется пометка indirect?

Зачем рекурсивному перечислению Swift требуется пометка indirect?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

indirect сообщает Swift, что рекурсивное значение нужно хранить через дополнительный уровень косвенности. Без этого компилятор не может определить конечный размер перечисления: каждый вложенный экземпляр содержал бы ещё один такой же экземпляр без ограниченной глубины.

Исторический контекст

Перечисления Swift обычно имеют размер, который можно определить на этапе компиляции по их вариантам и associated values. Это позволяет эффективно размещать значения и передавать их с семантикой value type.

Рекурсивные структуры данных, например деревья и AST, нарушают это предположение. Подход indirect решает проблему конечного представления, помещая рекурсивную часть за косвенной ссылкой, а не встраивая её непосредственно.

Постановка проблемы

Рассмотрим перечисление, один из вариантов которого содержит значение того же типа. Если вложенное значение встраивать напрямую, размер типа должен включать размер самого типа, который снова включает его размер. Возникает бесконечная зависимость размера, поэтому такой тип нельзя разместить и передать как обычное значение.

Ошибка проектирования здесь может проявиться ещё до выполнения программы: компилятор отклонит рекурсивное перечисление без необходимой косвенности. Попытка заменить перечисление классом устраняет проблему размера, но меняет семантику с value type на reference type.

Подробное решение

Пометка indirect заставляет Swift хранить рекурсивное содержимое отдельно, а само перечисление содержит управляемую ссылку на это содержимое. Поэтому размер основного значения остаётся конечным, независимо от глубины дерева.

indirect enum Tree { case leaf(Int) case branch(Tree, Tree) } let tree = Tree.branch(.leaf(1), .branch(.leaf(2), .leaf(3)))

В этом примере каждый branch может содержать другие Tree, потому что рекурсивная часть представлена косвенно. Само перечисление по-прежнему является значимым типом: присваивание и передача значения сохраняют value semantics, хотя конкретная реализация хранения может использовать внутреннюю косвенность и оптимизации.

indirect можно применить ко всему перечислению либо только к рекурсивным вариантам. Второй вариант может быть предпочтительнее, если часть случаев не требует косвенного хранения. Точный способ размещения в памяти не следует считать публичным контрактом Swift; гарантируется необходимость косвенного представления для корректного рекурсивного типа.

Альтернатива в виде класса проще, когда нужны идентичность объектов, совместное изменение узлов или циклические ссылки. Однако класс требует учитывать ARC, reference semantics и возможные циклы владения, тогда как рекурсивное перечисление лучше выражает неизменяемую структуру данных и сопоставление с вариантами.

Ситуация из практики

Команда реализует синтаксическое дерево выражений для анализатора. Варианты узла должны включать литерал и бинарную операцию, у которой есть левое и правое подвыражения.

Рассматривались два решения. Класс дал бы простую рекурсию и удобную идентичность узлов, но добавил бы ARC, ссылочную семантику и риск неожиданных общих изменений. Структура без косвенности не подходит, потому что рекурсивное поле делает размер типа бесконечным.

Выбрано рекурсивное перечисление с indirect. Оно сохраняет исчерпывающий switch, value semantics и ясное описание допустимых форм дерева. В результате преобразования и оптимизации получают предсказуемое поведение копирования, а ссылочную модель приходится вводить только при реальной необходимости совместно изменять узлы.

Что кандидаты часто упускают

  1. Обязательно ли помечать всё перечисление indirect?

Нет. Косвенность можно указать на уровне отдельных рекурсивных вариантов. Это позволяет оставить простые варианты непосредственно представленными, а рекурсивное содержимое хранить косвенно. Пометка всего перечисления проще для чтения, но может быть менее точной с точки зрения модели хранения.

  1. Делает ли indirect перечисление ссылочным типом?

Нет. indirect меняет способ представления рекурсивного содержимого, но не превращает перечисление в класс. Перечисление сохраняет семантику значения: его присваивание не означает, что две переменные обязательно являются двумя именами одного изменяемого объекта. При этом конкретные оптимизации копирования и внутреннее разделение хранения не должны использоваться как основание для изменения поведения программы.

  1. Можно ли заменить indirect необязательным рекурсивным полем?

Нет, это не универсальная замена. Необязательность ограничивает наличие значения, но не устраняет проблему рекурсивного размера, если тип всё равно содержит себя напрямую. Для рекурсивного перечисления нужна именно косвенность, предоставляемая indirect, либо изменение дизайна на ссылочный узел через класс.