Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Указатели

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 коде