% Сырые указатели
Стандартная библиотека Rust содержит ряд различных типов умных указателей, но среди них есть два типа, которые экстра-специальные. Большая часть безопасности в Rust является следствием проверок во время компиляции, но сырье указатели не имеют конкретных гарантий и являются небезопасными для использования.
*const T
и *mut T
в Rust называются «сырыми указателями» (raw pointers).
Иногда, при написании определенных видов библиотек, вам по какой-то причине
нужно обойти гарантии безопасности Rust. В этом случае, вы можете использовать
сырые указатели в реализации вашей библиотеки, вместе с тем предоставляя
безопасный интерфейс для пользователей. Например, *
указатели допускают
псевдонимы, позволяя им быть использованными для записи типов с разделяемой
собственности, и даже поточно-безопасные типы памяти (Rc<T>
и Arc<T>
типы и
реализован полностью в Rust).
Вот некоторые факты о сырых указателях, которые следует помнить и которые отличают их от других типов указателей. Они:
- не гарантируют, что они указывают на действительную область памяти, и не
гарантируют, что они является ненулевыми указателями (в отличие от
Box
и&
); - не имеют никакой автоматической очистки, в отличие от
Box
, и поэтому требуют ручного управления ресурсами; - это простые структуры данных (plain-old-data), то есть они не перемещают право
собственности, опять же в отличие от
Box
, следовательно, компилятор Rust не может защитить от ошибок, таких как использование освобождённой памяти (use- after-free); - лишены сроков жизни в какой-либо форме, в отличие от
&
, и поэтому компилятор не может делать выводы о висячих указателях; и - не имеют никаких гарантий относительно псевдонимизации или изменяемости, за
исключением изменений, недопустимых непосредственно для
*const T
.
Основы
Создание сырого указателя совершенно безопасно:
#![allow(unused)] fn main() { let x = 5; let raw = &x as *const i32; let mut y = 10; let raw_mut = &mut y as *mut i32; }
А вот его разыменование не является. Следующий код не будет работать:
let x = 5;
let raw = &x as *const i32;
println!("raw points at {}", *raw);
Он выдает такую ошибку:
error: dereference of unsafe pointer requires unsafe function or block [E0133]
println!("raw points at{}", *raw);
^~~~
Когда вы разыменовываете сырой указатель, вы принимаете на себя ответственность,
что он не указывает на что-то, что может быть некорректным. Таким образом, вы
должны использовать unsafe
:
#![allow(unused)] fn main() { let x = 5; let raw = &x as *const i32; let points_at = unsafe { *raw }; println!("raw points at {}", points_at); }
Для более подробной информации по операциям с сырыми указателями, обратитесь к API документации о них.
FFI
Сырые указатели полезны для FFI: *const T
и *mut T
в Rust приблизительно
соответствуют const T*
и T*
в C. Для более подробной информации об этом
обратитесь к главе FFI.
Ссылки и сырые указатели
Во время выполнения и сырой указатель, *
, и ссылка, указывающая на тот же
кусок данных, имеют одинаковое представление. По факту, ссылка &T
будет неявно
приведена к сырому указателю *const T
в безопасном коде, аналогично и для
вариантов mut
(оба приведения могут быть выполнены явно, с помощью,
соответственно, value as *const T
и value as *mut T
).
Переход в обратном направлении, от *const
к ссылке &
, не является безопасным.
Ссылка &T
всегда валидна, и поэтому, как минимум, сырой указатель *const T
должен указывать на правильный экземпляр типа T
. Кроме того, в результате
указатель должен удовлетворять правилам псевдонимизации и изменяемости ссылок.
Компилятор предполагает, что эти свойства верны для любых ссылок, независимо от
того, как они были созданы, и поэтому любое преобразование из сырых указателей
равносильно утверждению, что они соответствуют этим правилам. Программист
должен гарантировать это.
Рекомендуемым методом преобразования является
#![allow(unused)] fn main() { let i: u32 = 1; // explicit cast let p_imm: *const u32 = &i as *const u32; let mut m: u32 = 2; // implicit coercion let p_mut: *mut u32 = &mut m; unsafe { let ref_imm: &u32 = &*p_imm; let ref_mut: &mut u32 = &mut *p_mut; } }
Разыменование с помощью конструкции &*x
является более предпочтительным, чем с
использованием transmute
. Последнее является гораздо более мощным
инструментом, чем необходимо, а более ограниченное поведение сложнее
использовать неправильно. Например, она требует, чтобы x
представляет собой
указатель (в отличие от transmute
).