RustMiddleTechnical

Что такое raw pointers в Rust и когда их следует использовать?

Raw pointers (*const T / *mut T) — указатели без гарантий borrow checker; разыменование требует unsafe. Используются в FFI, ручном управлении памятью и нестандартных структурах данных.

Raw pointers в Rust

Raw pointers — это указатели *const T и *mut T, которые существуют вне системы владения и заимствования Rust. Компилятор не гарантирует их корректность: они могут быть нулевыми, висячими или указывать на освобождённую память. Разыменование raw pointer разрешено только внутри блока unsafe.

Отличия от ссылок

  • Не подчиняются правилу одного изменяемого или нескольких неизменяемых заимствований.
  • Не имеют времени жизни (lifetime), поэтому borrow checker их не отслеживает.
  • Могут быть нулевыми — std::ptr::null() и std::ptr::null_mut().
  • Разыменование через *ptr требует блока unsafe.
  • Арифметика указателей (ptr.add(n), ptr.offset(n)) разрешена только в unsafe.

Когда применяются

  • FFI: взаимодействие с C-библиотеками через extern "C".
  • Реализация нестандартных структур данных (связные списки, арены, интрузивные структуры).
  • Оптимизации, где гарантия корректности обеспечивается вручную.
  • Работа с std::mem::MaybeUninit для неинициализированной памяти.

Пример: FFI и ручное управление памятью

use std::alloc::{alloc, dealloc, Layout};
use std::ptr;

fn main() {
    let layout = Layout::new::<u64>();

    unsafe {
        // Выделяем память вручную
        let raw: *mut u64 = alloc(layout) as *mut u64;
        if raw.is_null() {
            panic!("allocation failed");
        }

        // Записываем значение через raw pointer
        ptr::write(raw, 42u64);

        // Читаем значение
        let value = ptr::read(raw);
        println!("value = {}", value); // value = 42

        // Освобождаем вручную
        dealloc(raw as *mut u8, layout);
    }
}

Пример: взаимодействие с C через FFI

extern "C" {
    fn strlen(s: *const u8) -> usize;
}

fn main() {
    let s = b"hello\0";
    let len = unsafe { strlen(s.as_ptr()) };
    println!("len = {}", len); // len = 5
}

Создание raw pointer из ссылки

fn main() {
    let mut x = 10i32;
    let r: *mut i32 = &mut x as *mut i32;

    unsafe {
        *r += 5;
    }
    println!("{}", x); // 15
}

Подводные камни

  • Use-after-free: raw pointer не отслеживает время жизни объекта, поэтому легко получить висячий указатель после освобождения памяти.
  • Нулевой указатель: разыменование std::ptr::null() вызывает неопределённое поведение (UB). Всегда проверяйте ptr.is_null().
  • Неверное выравнивание: ptr::read/ptr::write требуют правильного выравнивания. Для невыровненного доступа используйте ptr::read_unaligned.
  • Двойное освобождение: если несколько raw pointer указывают на одну область, освобождение через dealloc дважды — UB.
  • Гонки данных: *mut T не Send/Sync по умолчанию, но можно обойти это вручную, что открывает гонки данных.
  • Арифметика за пределами аллокации: ptr.add(n) за границами выделенного блока — UB, даже если не разыменовывается.
  • Замена Box::into_raw без Box::from_raw: если вызвать Box::into_raw и не вернуть владение через Box::from_raw, будет утечка памяти.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics