Сценарий: FFI возвращает указатель на буфер и его длину. Что нужно доказать перед созданием из них Rust-среза?
Перед созданием среза нужно доказать, что указатель и диапазон памяти удовлетворяют инвариантам Rust: указатель корректен, выровнен, указывает на инициализированные элементы одной аллокации, а весь диапазон доступен для чтения на протяжении времени жизни среза. Для пустого среза указатель всё равно должен быть ненулевым и правильно выровненным.
При FFI эти свойства обычно не проверяются автоматически: их гарантирует контракт внешней функции и безопасная обёртка. Если хотя бы одно условие нарушено, создание или последующее использование среза может привести к неопределённому поведению.
Raw pointers нужны Rust для взаимодействия с системными API, аппаратными ресурсами и кодом на C-подобных языках. Такие указатели не несут гарантий владения, времени жизни, уникальности доступа или корректности диапазона.
Срез &[T] удобнее raw pointer: он содержит указатель и длину, а код Rust может использовать его через безопасные операции. Однако преобразование raw pointer в ссылку переносит ответственность за соблюдение инвариантов с компилятора на программиста.
FFI-буфер может быть освобождён внешним кодом сразу после возврата, иметь меньший фактический размер, содержать неинициализированные данные или пересекать границу нескольких аллокаций. Числовая пара «указатель плюс длина» сама по себе не доказывает ни одного из этих свойств.
Неверное преобразование может привести к чтению за границами, использованию памяти после освобождения, нарушению выравнивания или созданию ссылки на неинициализированное значение. В Rust это не просто ошибка результата: такое поведение нарушает модель памяти и делает поведение всей программы неопределённым.
Для создания &[T] из указателя и длины должны выполняться следующие условия:
T, включая случай нулевой длины;len корректно инициализированных значений T;isize;Для &mut [T] добавляется более сильное требование: на соответствующий диапазон не должно существовать других активных ссылок или конкурирующего доступа. Это особенно важно, когда иностранный код может одновременно изменять тот же буфер.
Создание среза не копирует данные и не продлевает их время жизни. Если FFI-буфер принадлежит внешней библиотеке, безопасная обёртка должна либо удерживать ресурс живым на весь срок использования среза, либо немедленно скопировать данные во владение Rust.
Например, обёртка ниже ограничивает срок использования внешней памяти временем копирования. Её контракт всё равно требует, чтобы внешний код предоставил доступный диапазон байтов; Rust не может проверить это по одному raw pointer.
Проверка len и ненулевого указателя не доказывает принадлежность диапазона одной аллокации или его фактическую доступность. Поэтому такие проверки дополняют, но не заменяют договорённость с FFI и анализ жизненного цикла буфера.
Основной компромисс таков: заимствованный срез эффективен и не требует копирования, но связывает Rust-код с временем жизни и правилами доступа внешнего буфера. Копирование дороже по времени и памяти, зато обычно позволяет вернуть безопасный владеющий тип и разорвать эту зависимость.
Внешняя библиотека возвращает указатель на сетевой пакет и длину. Буфер принадлежит внутреннему пулу библиотеки и может быть переиспользован при следующем вызове обработки пакета.
Рассматривались два варианта. Первый — вернуть из Rust заимствованный срез: это исключает копирование, но требует гарантировать, что пул не переиспользует буфер и не освобождает его до окончания времени жизни среза. Второй — сразу скопировать байты в Vec<u8>: это добавляет копирование, зато результат больше не зависит от жизненного цикла FFI-буфера.
Выбран второй вариант, поскольку контракт библиотеки не гарантировал стабильность памяти после возврата из callback. В результате Rust-код получил собственное владение данными, а риск использования освобождённого или переиспользованного буфера остался внутри короткой проверяемой unsafe-обёртки.
Нет. Ненулевой указатель не доказывает корректное выравнивание, инициализацию элементов, доступность всего диапазона или принадлежность одной аллокации. Кроме того, для пустого среза указатель также должен удовлетворять требованиям ненулевого значения и выравнивания.
Нет. Срез является заимствованием и не управляет ресурсом, на который указывает. Если внешняя библиотека освободит или переиспользует буфер, срез станет недействительным, даже если его тип и время жизни формально позволяют продолжить использование.
Только если доказан весь необходимый контракт: буфер остаётся живым после callback, диапазон полностью доступен, данные инициализированы, а условия доступа действительно сохраняются весь срок жизни среза. Одного обещания «не менять» недостаточно, если библиотека может освободить буфер, изменить его размер или передать указатель на временную память; при сомнительном времени жизни следует копировать данные.