Функция возвращает raw pointer на локальную переменную. В какой момент такой указатель становится непригодным для доступа?
Raw pointer на локальную переменную становится недействительным после завершения функции, когда заканчивается время жизни этой переменной. Само возвращение указателя обычно допустимо, но любое чтение или запись по нему после выхода из функции приводит к неопределённому поведению, даже если числовой адрес ещё выглядит корректным.
Raw pointers нужны Rust для низкоуровневого программирования, взаимодействия с C и реализации механизмов, которые невозможно выразить обычными ссылками. В отличие от ссылок, они не заставляют компилятор проверять время жизни и корректность доступа.
Такой контроль переносится на разработчика: unsafe позволяет выполнить операцию, но не продлевает время жизни объекта и не превращает локальную память в долгоживущую. Это сохраняет возможность системного программирования, не ослабляя гарантии безопасного Rust-кода.
Локальная переменная обычно размещается в кадре стека текущей функции. После возврата кадр перестаёт быть действующим хранилищем этой переменной, поэтому указатель на неё становится висячим.
Опасность может быть незаметной: стековый слот иногда сохраняет прежнее содержимое или позднее используется повторно для другой переменной. Поэтому программа может некоторое время печатать ожидаемое значение, но это не делает доступ корректным. Неопределённое поведение возникает из-за самого недействительного доступа, а не только из-за наблюдаемого неправильного результата.
Создание raw pointer не продлевает время жизни объекта. Операции разыменования, чтения и записи по нему должны выполняться в unsafe, но наличие unsafe означает лишь, что программист взял на себя проверку инвариантов; оно не исправляет висячий указатель.
В примере указатель возвращается, но после выхода из pointer_to_local объект value больше не существует. Разыменование pointer в main невалидно независимо от того, какое значение фактически окажется в выводе.
Безопасный способ вернуть долгоживущий объект — передать владение хранилищем с подходящим временем жизни, например вернуть значение непосредственно или выделить объект в куче через Box. При передаче raw pointer из Box необходимо отдельно определить, кто сохраняет владение и кто обязан выполнить корректное освобождение.
Нельзя исправить проблему простым преобразованием указателя в usize, использованием addr_of! или размещением разыменования в другом unsafe-блоке. Эти операции меняют представление или место проверки, но не продлевают время жизни объекта.
При написании callback-интерфейса разработчик возвращает C-коду указатель на временную структуру, созданную внутри функции. Вариант с локальной переменной прост и не требует выделения памяти, но указатель становится недействительным сразу после возврата и непригоден для отложенного callback-вызова.
Вариант с глобальным или статическим хранилищем продлевает время жизни, однако создаёт проблемы с синхронизацией, повторными вызовами и освобождением ресурсов. Вариант с Box::into_raw передаёт объект в куче и позволяет явно определить момент возврата владения через соответствующее восстановление Box, но требует строгого контракта: указатель должен быть использован ровно в пределах разрешённого времени и освобождён совместимым способом.
Практически выбирают выделение в куче или явное владение объектом со стороны вызывающего кода. В контракте фиксируют, может ли внешний код хранить указатель, когда он становится недействительным и кто отвечает за освобождение. Это устраняет зависимость от случайного содержимого и повторного использования стека.
Является ли само возвращение raw pointer на локальную переменную неопределённым поведением?
Обычно нет: формирование и передача значения raw pointer сами по себе не требуют, чтобы объект продолжал существовать. Проблема начинается при использовании указателя для доступа к уже завершившему время жизни объекту. Однако безопасный API не должен выдавать такой указатель без контракта, прямо запрещающего его использование после возврата.
Можно ли разыменовать указатель, если по тому же адресу позже размещён новый объект того же типа?
Нет, совпадение адреса не восстанавливает корректность старого указателя. Старый объект завершил время жизни, а новый объект имеет отдельный жизненный цикл; для доступа требуется указатель, корректно относящийся к новому объекту, с соблюдением его инициализации, выравнивания и правил владения. Простое совпадение числового адреса не является таким доказательством.
Почему Box::into_raw может решить проблему, но не делает любой raw pointer безопасным?
Box::into_raw предотвращает автоматическое уничтожение объекта при выходе из функции и передаёт указатель на выделение в куче. Но вызывающая сторона должна сохранить указатель до тех пор, пока объект нужен, не обращаться к нему после освобождения и вернуть владение способом, совместимым с исходным выделением. Если указатель освободить дважды, использовать после восстановления Box или передать его коду с несовместимым аллокатором, возникнет другая ошибка безопасности.