После вызова C-функции Rust хочет передать буфер в Vec::from_raw_parts. Какой контракт владения и размещения должен быть подтверждён до этого вызова?
unsafe extern "C" {
fn c_alloc(size: usize) -> *mut u8;
}
fn receive(n: usize) -> Vec<u8> {
let p = unsafe { c_alloc(n) };
unsafe { Vec::from_raw_parts(p, n, n) }
}
Vec::from_raw_parts допустим только тогда, когда указатель указывает на память, которую Rust вправе освободить своим аллокатором, а len, cap, выравнивание и размер точно соответствуют выделению. Первые len элементов должны быть полностью инициализированы. Сам факт, что C выделил достаточно байт, этого контракта не выполняет.
Vec::from_raw_parts нужен для передачи владения буфером без копирования: указатель, длина и вместимость снова объединяются в управляемый Rust-контейнер. Такой механизм особенно полезен при обмене данными между низкоуровневым Rust-кодом и FFI.
Однако Vec не является нейтральным представлением произвольной C-памяти. При уничтожении он освобождает буфер по правилам Rust, поэтому восстановление Vec одновременно восстанавливает и обязанность корректно освободить память.
В примере неизвестно, каким аллокатором работает c_alloc, действительно ли его выделение имеет нужное выравнивание, равен ли фактический объём n, инициализированы ли все n элементов и можно ли передавать Rust владение этой памятью.
Если C использует malloc, а Rust затем уничтожит Vec, возможен вызов несовместимого освобождения. Если n превышает фактически инициализированное количество элементов, Rust прочитает неинициализированную память; если cap неверен, при последующем росте или освобождении будет использована неправильная раскладка.
Перед вызовом нужно доказать все следующие условия:
p указывает на выделенный блок, совместимый с освобождением через аллокатор Rust;p не является нулевым и корректно выровнен для u8;len <= cap;len элементов типа u8 инициализированы;cap * size_of::<u8>() и нужному выравниванию;Vec, включая ограничение на общий размер объекта;После успешного вызова Vec::from_raw_parts отвечает за память. Нельзя затем передавать тот же указатель в free, вызывать C-деструктор или повторно создавать из него другой владеющий контейнер.
Для обычного C-буфера безопаснее скопировать данные в Rust: временно проверить указатель, длину и инициализированность, создать срез через from_raw_parts, затем вызвать .to_vec(). В этом случае C сохраняет владение исходным блоком, а Rust владеет независимой копией.
Если копирование недопустимо, API должен явно согласовать аллокатор и владельца. Например, Rust может передать C указатель, полученный из Vec, а C обязан вернуть его в специальную Rust-функцию освобождения с исходными capacity и типом. Параметр len при освобождении не должен использоваться как признак числа инициализированных элементов; важны исходные capacity и корректная раскладка выделения.
Для нулевой длины нельзя безоговорочно передавать нулевой указатель: требования Vec к указателю остаются значимыми. Нулевой результат C нужно обработать отдельно, а для пустого Vec использовать обычный Vec::new() либо корректный ненулевой dangling-указатель, предусмотренный Rust-абстракцией.
Минимальная схема передачи Rust-буфера без копирования выглядит так:
Такой указатель можно передать C только вместе с контрактом: C не освобождает его самостоятельно, не выходит за capacity и возвращает владение функции Rust, которая восстановит Vec с теми же параметрами.
C-библиотека возвращает массив, выделенный через собственный пул памяти, и предоставляет функцию c_free. Вариант с Vec::from_raw_parts выглядит эффективным, но неверен: Vec при уничтожении не вызовет c_free, поэтому даже совпадение размера и выравнивания не устраняет несовместимость владения.
Можно скопировать данные в Vec: это проще всего доказать и не связывает время жизни Rust-контейнера с C-пулом, но требует O(n) времени и дополнительной памяти. Можно оставить указатель в обёртке с ручным вызовом c_free: копирования нет, но приходится самостоятельно поддерживать длину, время жизни и потокобезопасность.
Практичный выбор — обёртка, которая хранит указатель, длину и принадлежность C-пулу, а в Drop вызывает именно c_free. Если буфер нужен после освобождения C-ресурса или должен изменяться как обычный Vec, выполняется явная копия. Это разделяет две модели владения вместо опасной попытки выдать C-память за Rust-выделение.
Достаточно ли передать len == cap, если C выделил ровно нужное число байт?
Нет. Равенство длины и вместимости решает только одно числовое условие. Нужно также доказать совместимость аллокатора, выравнивание, инициализацию всех элементов и передачу единственного владения. Несовместимое освобождение остаётся неопределённым поведением даже при идеально совпадающем размере.
Можно ли указать len = 0, чтобы обойти сомнение в инициализации C-буфера?
Это снимает требование об инициализации элементов, но не делает память автоматически пригодной для Vec. Указатель, вместимость, аллокатор и раскладка всё равно должны быть корректными. Кроме того, при последующем set_len Rust будет считать новые элементы инициализированными, поэтому их нужно предварительно фактически записать.
Почему нельзя восстановить C-буфер сначала в Vec<u8>, а затем передать его обратно в C для освобождения?
Потому что после from_raw_parts владельцем становится Vec, а C уже не должен самостоятельно освобождать тот же блок. Передача указателя в c_free создаёт двойное освобождение или использование несовместимого аллокатора. Если освобождать должен C, буфер нужно оставить под контролем C и описать это в отдельной обёртке без восстановления владеющего Vec.