% Сырые указатели

Стандартная библиотека 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_variables)]
#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_variables)]
#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_variables)]
#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).