Указатели
unsafe
В языке Rust имеются указатели, которые работают практически так же, как и в C. Но, как мы сказали в самом начале книги, работа с указателями запрещена в безопасном Rust. Именно поэтому в этой главе мы активно будем использовать unsafe.
В главе про Статические переменные мы уже видели unsafe блок:
#![allow(unused)]
fn main() {
unsafe {
код
}
}
Внутри такого блока можно использовать небезопасные операции, включая работу с указателями.
Также можно пометить целую функцию ключевым словом unsafe.
#![allow(unused)]
fn main() {
unsafe fn функция() {
...
}
}
В таком случае всё тело функции становится unsafe блоком. Причём такую unsafe функцию можно вызывать только либо из unsafe блока, либо из другой unsafe функции.
Работа с указателями
Как и в C, в Rust указатель является переменной, которая хранит адрес в памяти. При этом на уровне исходного кода программы у указателя имеется тип, представляющий информацию о типе значения, чей адрес хранится в указателе.
Тип указателя получается путем добавления приставки *const (для немутабельного) или *mut (для мутабельного указателя) перед тем типом, на чьё значения ссылается указатель.
Например, если переменная имеет тип i32, то немутабельный указатель на неё будет иметь тип *const i32.
#![allow(unused)]
fn main() {
let p1: *const i32; // немутабельный указатель на i32
let p2: *mut i32; // мутабельный указатель на i32
let p3: *mut String; // мутабельный указатель на строку
}
Есть несколько способов получить указатель на объект:
1) Преобразованием из ссылки
#![allow(unused)]
fn main() {
let mut v: i32 = 5;
let const_ptr: *const i32 = &v as *const i32;
let mut_ptr: *mut i32 = &mut v as *mut i32;
}
2) При помощи &raw (пришёл на смену макросам addr_of и addr_of_mut).
#![allow(unused)]
fn main() {
let mut v: i32 = 5;
let const_ptr: *const i32 = &raw const v;
let mut_ptr: *mut i32 = &raw mut v;
}
3) Макросы addr_of и addr_of_mut (более старый вариант).
#![allow(unused)]
fn main() {
let mut v: i32 = 5;
let const_ptr: *const i32 = std::ptr::addr_of!(v);
let mut_ptr: *mut i32 = std::ptr::addr_of_mut!(v);
}
Для того, чтобы разыменовать указатель (обратиться к значению по адресу), как и в C, используется оператор *.
Рассмотрим простой пример.
fn main() {
let a = 5;
let ptr = (&a) as *const i32; // берём ссылку и преобразуём её в указатель
unsafe {
// разыменовываем указатель, чтобы получить значение переменной
println!("{}", *ptr); // 5
}
}
Приведение ссылки к указателю является безопасной операцией и может выполняться вне блока unsafe. Но разыменовывание указателя или преобразование указателя в ссылку требуют unsafe.
Обход ограничения ссылок
Напомним, что в Rust в каждой точке программы на любой объект мы можем иметь либо одну мутабельную ссылку, либо сколько угодно немутабельных. В подавляющем большинстве ситуаций, особенно при написании бэкендов, это ограничение никак не мешает. Однако при написании структур данных или алгоритмов часто может понадобиться иметь более одной мутабельной ссылки на один и тот же объект.
Например, в двунаправленном списке нам одновременно нужны две мутабельные ссылки на одни и те же элементы: одна ссылка со стороны головы списка, другая с хвоста.
Другой пример — сортировка слиянием (merge sort), которая подразумевает разделение исходной последовательности на участки, каждый из которых сортируется отдельно, а следовательно, должен иметь свою мутабельную ссылку.
unsafe блок не позволяет напрямую нарушить правило безопасности ссылок, однако unsafe позволяет создать дополнительную ссылку через промежуточный указатель:
мутабельная ссылка → указатель → еще одна мутабельная ссылка.
fn main() {
let mut a = 5;
unsafe {
let r1: &mut i32 = &mut a; // первая мутабельная ссылка
let ptr: *mut i32 = r1 as *mut i32; // мутабельный указатель
let r2: &mut i32 = ptr.as_mut().unwrap(); // указатель во вторую ссылку
inc(r1);
inc(r2);
}
println!("{a}"); // 7
}
fn inc(a: &mut i32) {
*a = *a + 1;
}
Разумеется, этот приём можно использовать только при крайней необходимости. Также настоятельно рекомендуется:
- хорошо покрывать тестами код, использующий unsafe
- осуществлять диагностику кода при помощи Miri — утилиты для поиска проблем в unsafe коде