Кому должна быть возвращена память, выделенная Rust и переданная через FFI в виде raw pointer?
Память должна освобождаться тем механизмом, которому соответствует её выделение: память, созданную через Rust-аллокатор, обычно возвращают в Rust через специально предоставленную функцию освобождения. Нельзя без отдельного контракта вызывать из C free для указателя, полученного из Box или Vec.
Передача raw pointer сама по себе не описывает владение. FFI-контракт обязан явно определить, кто отвечает за время жизни объекта, сколько раз его можно освободить и каким именно способом.
Модель владения Rust рассчитана на код, который компилятор видит целиком: он отслеживает перемещения, заимствования и момент уничтожения значения. На границе FFI компилятор не может проверить действия C-кода, поэтому raw pointer используется как нейтральное представление адреса без автоматического управления временем жизни.
Такой подход позволяет взаимодействовать с C-библиотеками, но переносит часть инвариантов из системы типов в явный двусторонний контракт. В частности, нужно отдельно описывать происхождение памяти и допустимый способ её освобождения.
Предположим, Rust передал C указатель на объект, созданный через Box. Если C вызовет free, а затем Rust попытается уничтожить тот же объект, возможны несовместимость аллокаторов, двойное освобождение и неопределённое поведение.
Обратная ошибка тоже опасна: Rust может вызвать Box::from_raw для указателя, который был выделен через malloc или уже освобождён. Дополнительные риски возникают, если C сохраняет указатель после окончания оговорённого срока, освобождает его несколько раз или передаёт его обратно с изменённым адресом.
Безопасный контракт обычно выбирает один из двух вариантов. Либо Rust полностью владеет выделением и предоставляет C парные функции создания и освобождения, либо память выделяется и освобождается средствами C, а Rust работает с ней только в рамках явно проверенных условий.
Для объекта, созданного через Box::into_raw, обратное преобразование должно выполняться ровно один раз и только для исходного указателя. Box::from_raw восстанавливает ответственность Rust за уничтожение объекта; после этого указатель нельзя повторно использовать или передавать в ещё одну операцию освобождения.
Минимальная схема парных функций выглядит так:
create_value передаёт владение указателем наружу, а destroy_value возвращает его Rust. Проверка на null защищает только от нулевого указателя; она не доказывает, что указатель действительно получен из соответствующего Box и ещё не освобождён.
Для Vec одного адреса недостаточно: при восстановлении нужно знать исходные длину и вместимость, а также соблюдать требования к выравниванию и корректности выделения. Если память выделена C-аллокатором, её следует освобождать согласованным C-механизмом, например через отдельную C-функцию, а не произвольным Rust-конструктором.
Иногда практичнее не передавать владение вообще: Rust сохраняет объект у себя, а C получает временный указатель, действующий только до определённого вызова. Это упрощает освобождение, но запрещает C хранить указатель дольше срока и может потребовать синхронизации при многопоточном доступе.
Rust-библиотека передаёт C-кодеку большой буфер. Команда сначала рассматривала передачу Vec через указатель, длину и вместимость с последующим восстановлением в Rust. Плюс этого варианта — отсутствие копирования; минусы — необходимость безошибочно сохранить все параметры, запрет на самостоятельное изменение структуры буфера и сложный контракт освобождения.
Второй вариант — выделять буфер через C-аллокатор и возвращать его через C-функцию освобождения. Он лучше согласуется с существующим кодеком, но усложняет использование буфера в Rust и требует проверять результаты выделения и границы доступа.
Выбрали первый вариант только для синхронного вызова: Rust сохраняет Vec живым до возврата из функции, C не сохраняет указатель и не освобождает его. Для асинхронного API применили отдельные функции создания и уничтожения, чтобы жизненный цикл был явным. Это устранило двойное освобождение и позволило сохранить передачу без копирования там, где она действительно нужна.
Нет. Нужно определить, кто вызывает функцию освобождения, в какой момент, допускается ли повторный вызов и может ли C продолжать доступ к объекту после него. Также должны быть согласованы ABI, тип указателя, выравнивание и условия доступа из разных потоков.
Box::from_raw на преобразование указателя в Box, если указатель пришёл из C?Только если C передал указатель, созданный совместимым способом и с точно соответствующими правилами владения. Сам факт, что адрес указывает на корректно размещённый объект, недостаточен: важны происхождение выделения, ожидаемый тип и гарантия, что объект ещё не уничтожен.
Изменение допустимо лишь при наличии подходящего уникального доступа и корректного диапазона памяти. Сохранение указателя превращает временный доступ в долгосрочное владение или заимствование, поэтому Rust обязан гарантировать существование объекта и отсутствие конфликтующего доступа до момента явного освобождения или отзыва указателя. Если это невозможно доказать, следует копировать данные либо использовать отдельный объект с явно управляемым временем жизни.