Разберите, какие дополнительные инварианты возникают в момент превращения raw pointer в ссылку Rust.
Превращение raw pointer в ссылку Rust означает, что программа немедленно принимает на себя все инварианты ссылок: указатель должен быть ненулевым, правильно выровненным, указывать на корректно инициализированный объект нужного типа и оставаться валидным в течение времени жизни ссылки. Кроме того, должны соблюдаться правила доступа Rust: нельзя создавать конфликтующие ссылки или ссылку, несовместимую с фактическим временем жизни объекта.
unsafe не проверяет эти условия и не ослабляет их. Он лишь разрешает выполнить операцию, ответственность за доказательство корректности лежит на разработчике.
Безопасная модель Rust строится вокруг ссылок, владения и заимствований. Компилятор может оптимизировать код, полагаясь на то, что корректная ссылка не является null, выровнена, указывает на живой объект и соблюдает правила алиасинга.
Для взаимодействия с C, аппаратными буферами и низкоуровневыми механизмами Rust предоставляет raw pointers. Они не несут тех же гарантий, что ссылки, поэтому позволяют описывать внешнюю память, но требуют явного восстановления инвариантов перед использованием в безопасной модели Rust.
Raw pointer может быть получен из FFI, содержать null, указывать на освобождённую память, иметь неподходящее выравнивание или ссылаться на область, где ещё нет инициализированного значения нужного типа. Сам тип указателя не сообщает компилятору, какие из этих условий выполнены.
Если такой указатель превратить в ссылку, компилятор начинает считать все инварианты ссылки истинными. Нарушение этих предположений может привести к неопределённому поведению, даже если ссылка позднее явно не разыменовывается: уже её создание позволяет оптимизатору опираться на недоказанные свойства.
Минимальный пример механизма выглядит так:
Операция &*ptr требует unsafe, потому что разыменовывает raw pointer и создаёт обычную ссылку. Вызывающая сторона должна гарантировать, что ptr не null, корректно выровнен для u32, указывает на живой и инициализированный u32, а доступ через эту ссылку допустим в течение всей операции.
Ссылка также должна иметь реалистичное время жизни. Нельзя вернуть ссылку на память, которая будет освобождена сразу после вызова FFI, или искусственно объявить её живущей дольше объекта. Параметр времени жизни в сигнатуре не продлевает жизнь памяти — он только описывает уже существующую гарантию.
Особенно важно различать создание ссылки и работу с raw pointer. Если внешняя память может быть невыравненной, нельзя сначала создать ссылку, а затем попытаться безопасно прочитать данные. Для такой ситуации применяют операции над raw pointer, допускающие невыравненное чтение, либо копируют байты в корректно выровненное значение.
Если инварианты ссылок доказать нельзя, лучше не создавать ссылку вообще. Обёртка над FFI может оставаться unsafe, выполнять проверки и использовать raw-pointer операции, явно фиксируя в документации требования к указателю.
C-библиотека возвращает указатель на упакованный сетевой заголовок. Поле находится по адресу, который не обязан быть выровнен для соответствующего целочисленного типа, хотя сами байты доступны для чтения.
Рассматривались два варианта. Преобразование указателя в ссылку было бы коротким, но некорректным: ссылка требует подходящего выравнивания, поэтому нарушение уже на этапе её создания делает решение недопустимым. Копирование байтов во временный буфер безопаснее и проще для сложного формата, но добавляет копирование и требует отдельного разбора представления данных.
Выбран вариант чтения через raw pointer с операцией, предназначенной для невыравненных данных, при условии, что диапазон памяти принадлежит вызывающей библиотеке и содержит инициализированные байты нужного размера. В результате ссылка не создаётся, требование выравнивания не нарушается, а границы и длительность доступности буфера проверяются в обёртке FFI.
Нет. Ненулевой адрес — только одно из условий. Нужны корректное выравнивание, живой объект подходящего типа, инициализированное значение, допустимый доступ и отсутствие нарушения правил алиасинга. Кроме того, память должна оставаться доступной в течение времени жизни создаваемой ссылки.
Стабильность числового адреса недостаточна. C-код должен также гарантировать, что объект не будет освобождён, изменён несовместимым образом или доступен через конфликтующий способ, пока существует ссылка Rust. Нельзя автоматически считать произвольный C-буфер объектом Rust нужного типа только потому, что его адрес сохраняется.
Даже краткоживущая ссылка обязана быть корректной в момент создания. Временность не отменяет требований к null, выравниванию, инициализации и алиасингу. Если эти свойства не доказаны, ссылка уже не является допустимым представлением памяти Rust; следует выполнять операцию непосредственно через raw pointer или сначала скопировать данные в корректный объект.