Почему аннотация времени жизни не делает самоссылочную структуру безопасной для перемещения?
Аннотация времени жизни описывает, как долго ссылка внутри структуры может быть действительной, но не фиксирует адрес самой структуры и не запрещает её перемещение. Поэтому она не решает проблему самоссылочности: при перемещении структуры поле-владелец может оказаться по новому адресу, тогда как внутренняя ссылка продолжит указывать на старое расположение.
Система владения и заимствований Rust должна разрешать безопасную работу без сборщика мусора и без неявных runtime-проверок. Для этого компилятор проверяет не только срок действия ссылок, но и возможность перемещения значений, поскольку перемещение может изменить их адрес в памяти.
Обычные ссылки хорошо описывают отношения между независимыми значениями. Самоссылочные структуры сложнее: одно поле владеет данными, а другое поле указывает внутрь этого же объекта. Для них требуется дополнительный механизм, учитывающий стабильность адреса.
Рассмотрим структуру, которая владеет строкой и хранит ссылку на её часть:
Здесь ссылка part создана для отдельной переменной text, а затем text перемещается в структуру. Аннотация 'a может описать допустимый срок ссылки, но не превращает ссылку на прежнее значение в ссылку на новое поле структуры.
Даже гипотетически корректно созданную самоссылочную структуру опасно свободно перемещать: перемещение изменяет адрес объекта, а ссылка внутри него должна оставаться действительной. Следовательно, проблема состоит не только в длительности заимствования, но и в стабильности адреса.
Параметр времени жизни у SelfRef<'a> означает: ссылка part должна оставаться действительной в течение 'a. Он связывает ссылку с заимствованными данными, но не утверждает, что text находится внутри той же структуры, не продлевает срок жизни данных и не запрещает перемещение SelfRef.
Для безопасной самоссылочной структуры нужны дополнительные гарантии:
Pin помогает запретить перемещение уже закреплённого значения, но сам по себе не создаёт внутреннюю ссылку и не делает произвольную инициализацию самоссылочной структуры безопасной. На практике используют специальные библиотеки самоссылочных структур, тщательно разработанные unsafe-абстракции или меняют представление данных.
Часто проблему решают без внутренней ссылки: структура хранит собственный буфер и диапазон позиций, а ссылку создаёт только на время вызова метода. Это устраняет адресную зависимость и обычно упрощает перемещение, владение и уничтожение объекта.
Парсер хочет хранить исходный текст и заранее вычисленный фрагмент этого текста. Рассматривались три варианта. Копирование фрагмента в отдельную String проще всего и полностью безопасно, но увеличивает потребление памяти и стоимость копирования. Хранение ссылки внутри объекта экономит копирование, однако требует сложной самоссылочной конструкции со стабильным адресом и строгими правилами доступа. Хранение диапазона байтов или символов не содержит ссылки и позволяет получать фрагмент по запросу.
Для обычного парсера выбран вариант с диапазоном: объект владеет исходным текстом, а метод временно возвращает срез. Такой дизайн сохраняет единственного владельца, не зависит от перемещения объекта и не требует unsafe; его компромисс — необходимость проверять корректность границ и повторно формировать ссылку при доступе.
1. Достаточно ли поместить объект в Box, чтобы самоссылочная структура стала безопасной?
Нет. Box стабилизирует адрес размещённого объекта после помещения в него, но не решает корректно задачу построения внутренней ссылки и не запрещает все операции, способные нарушить инварианты. Кроме того, перемещение самого содержимого внутри Box или неправильная работа с инициализацией всё ещё могут быть проблемой. Нужна полная абстракция, гарантирующая порядок создания, закрепление и доступ к самоссылочным полям.
2. Достаточно ли указать для внутренней ссылки время жизни 'static?
Нет. 'static означает, что данные могут быть действительны до завершения программы, но не означает, что они находятся по неизменному адресу внутри конкретного объекта. Аннотация не превращает поле String в статическое хранилище и не устраняет зависимость ссылки от расположения владельца. Попытка использовать 'static для обычных данных лишь делает требование строже и обычно приводит к ошибке компиляции.
3. Может ли Pin в одиночку безопасно создать самоссылочный объект?
Нет. Pin решает задачу запрета перемещения после закрепления, но не предоставляет автоматически безопасный способ получить ссылку из одного поля на другое. Сначала нужно корректно построить объект, обеспечить нужный порядок инициализации и доказать, что дальнейшие операции сохраняют инварианты. Поэтому обычно применяют готовую безопасную оболочку или тщательно изолированный unsafe-код, а не используют Pin как замену lifetime-аннотациям и правилам владения.