Что именно перестаёт гарантировать Rust после преобразования ссылки в сырой указатель? Рассмотрите фрагмент:
fn main() {
let value = 42;
let ptr: *const i32 = &value;
unsafe {
println!("{}", *ptr);
}
}
После преобразования ссылки в сырой указатель Rust перестаёт отслеживать для него время жизни, уникальность доступа и связанность с исходным владельцем. Само создание указателя обычно безопасно, но его разыменование требует unsafe: программист обязан доказать, что указатель не нулевой, корректно выровнен, указывает на живой объект подходящего типа и не нарушает правил доступа к памяти.
В примере разыменование корректно, потому что value живёт до конца main, а указатель используется внутри этой области.
Владение и заимствование предназначены для автоматической проверки безопасного доступа к памяти без сборщика мусора. Однако системному коду нужны операции, которые невозможно выразить обычными ссылками: взаимодействие с C-библиотеками, операционной системой, аппаратными регистрами и вручную управляемой памятью.
Сырые указатели предоставляют такой низкоуровневый доступ, но выводят часть проверки из компилятора. unsafe служит явной границей, за которой ответственность за инварианты лежит на разработчике.
Ссылка &T содержит проверяемую компилятором гарантию: объект существует, доступ к нему согласован с правилами заимствования, а ссылка не используется после окончания её времени жизни. Тип *const T или *mut T таких гарантий сам по себе не предоставляет.
Если сохранить указатель после уничтожения объекта, разыменовать нулевой или некорректно выровненный адрес либо одновременно изменить данные и читать их через конфликтующие указатели, программа получает неопределённое поведение. Это не обычная ошибка Rust, которую можно безопасно обработать во время выполнения.
Преобразование ссылки в сырой указатель не продлевает жизнь объекта и не перемещает его:
В момент создания ptr объект ещё жив. Но компилятор больше не анализирует, как долго указатель будет использоваться. Поэтому такой указатель можно скопировать, сохранить в структуре или передать в unsafe-функцию, однако каждая операция разыменования должна быть обоснована.
Для корректного разыменования обычно необходимы следующие условия:
T;*const T выражает намерение читать через указатель, а *mut T допускает запись синтаксически. Однако сам тип *mut T не гарантирует, что запись безопасна: все перечисленные условия всё равно должны выполняться.
Блок unsafe не делает недействительный указатель безопасным и не отключает правила процессора. Он лишь разрешает компилятору принять код, корректность которого невозможно доказать средствами обычной системы типов. Поэтому unsafe-код обычно изолируют в небольшой функции с чётко описанными предусловиями, оставляя безопасный интерфейс для остальной программы.
Модуль получает адрес буфера от внешней C-библиотеки. Вариант с обычной ссылкой удобнее и безопаснее, но применим только после проверки, что библиотека действительно возвращает живой буфер нужного размера и с подходящим временем использования. Вариант с сырым указателем напрямую соответствует FFI-интерфейсу, но требует вручную проверять указатель при каждом доступе и учитывать правила владения, установленные внешней библиотекой.
Выбранное решение — спрятать указатель внутри небольшого адаптера. Адаптер проверяет null, размер и время жизни буфера при создании, а наружу возвращает безопасные срезы только пока эти гарантии сохраняются. Это сложнее, чем передача &[u8], но локализует unsafe-код и не распространяет ручные проверки по всему приложению.
Безопасно ли создать сырой указатель из нулевого адреса?
Да, само создание значения *const T или *mut T, включая нулевой указатель, не является разыменованием. Опасность возникает при попытке прочитать или записать память по этому адресу. Проверка на null необходима, но сама по себе не доказывает, что ненулевой адрес указывает на живой и корректно выровненный объект.
Продлевает ли сырой указатель время жизни исходного значения?
Нет. Указатель не является заимствованием в смысле, который анализирует borrow checker. После уничтожения объекта указатель может остаться численно прежним, но стать висячим; его разыменование будет неопределённым поведением. Продолжить использование можно только при отдельной гарантии, что соответствующая память управляется другим механизмом и объект всё ещё существует.
Гарантирует ли блок unsafe, что операция внутри него корректна?
Нет. unsafe только разрешает операции, для которых компилятор не может автоматически подтвердить безопасность. Ответственность за предусловия остаётся у программиста: нужно доказать валидность указателя, корректность типа, отсутствие конфликтующего доступа и правильный срок жизни памяти. Если хотя бы одно условие нарушено, наличие unsafe не превращает поведение в определённое.