Работа с кучей в Rust

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Продолжаем изучать Rust нетрадиционным способом. В этом раз будем разбираться в нюансах работы с кучей, для их понимания понадобятся: сырые указатели, выделение памяти в куче, размер экземпляра типа, запись значений в кучу и чтение из нее, const и static, unit-like структуры, переопределение глобального аллокатора, печать стека вызовов.


Это, определенно, overkill для одной статьи, а вот половину списка вполне можно освоить.


  • Предыдущая часть: Времена и структуры
  • Начало и содержание: Владение

Сырые указатели (Raw Pointers)


Указатель на неизменяемое значение:


    let i: i32 = 10;
    let pi = &i as *const i32;
    unsafe {
        dbg!(*pi);
    }

Указатель на изменяемое значение:


    let mut i: i32 = 10;
    let p_i = &mut i as *mut i32;
    unsafe {
        *p_i = 20;
        println!("*p_i: {}", *p_i)
    }

Против правил:


    let i: i32 = 0x_10_20_30_40;    
    let p_i = &i as *const _ as *mut i16;
    unsafe{
         *p_i = 0x_70_80;
         *p_i.offset(1) = 0x_50_60;
    }
    println!("i: {:x}", i);

  • Брать адреса можно сколько угодно, а вот разыменование указателя — опасная затея, так что добро пожаловать на территорию unsafe{}
  • Для случаев с нестандартным выравниванием надо использовать ptr::addr_of!() / ptr::addr_of_mut!()
  • Документация по методам сырых указателей: primitive.pointer

Выделение и освобождение памяти


Через std::alloc::alloc(), std::alloc::dealloc():


#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main(){
    let ppoint = alloc_t::<Point>();

    unsafe {
        (*ppoint).x = 10;
        (*ppoint).y = 20;
        println!("*ppoint: {:?}", *ppoint);
    }

    dealloc_t(ppoint);
}
fn alloc_t<T> () -> * mut T{
    let layout = std::alloc::Layout::new::<T>();
    unsafe {
        let res = std::alloc::alloc(layout) as *mut T;
        if res.is_null() {
            std::alloc::handle_alloc_error(layout);
        }
        return res;
    }
}
fn dealloc_t<T> (p: *mut T) {
    let layout = std::alloc::Layout::new::<T>();
    unsafe {
        std::alloc::dealloc(p as *mut u8, layout);
    }
}

  • Пара alloc() / dealloc() указана в The Rustonomicon при разборе RawVec
  • Nomicon то ли отстает, то ли упрощает
  • Как обстоят дела на самом деле с RawVec, рассмотрим позже, для этого нужны неведомые пока (в рамках серии статей) конструкции языка
  • Вызов handle_alloc_error — рекомендованный ("...are encouraged to call this function") способ обработки ошибок выделения памяти
  • handle_alloc_error() имеет сигнатуру pub fn handle_alloc_error(layout: Layout) -> ! — "фатальная" функция, из таких не возвращаются

См. также:


  • Вариант через обычный malloc

Размер экземпляра типа


Научившись выделать память полезно понимать, на что она пойдет. По отношению к размеру типы бывают:


1. Sized Types. Их размер известен во время компиляции и можно создать экземпляр типа:


...
    dbg!(mem::size_of::<bool>());
    dbg!(mem::align_of::<bool>());
    dbg!(mem::size_of::<[i32; 50]>());
    dbg!(mem::align_of::<[i32; 50]>());
    dbg!(mem::size_of::<PointTuple>());
    dbg!(mem::align_of::<PointTuple>());
...    

Пора сказать пару слов про кортеж (tuple). Это структура с безымянными полями:


#[derive(Debug)]
struct PointTuple(i32, i32);

fn main() {
    let mut pt = PointTuple(10, 20);
    pt.0 = 100;
    pt.1 = 200;
    dbg!(pt);
}

2. Zero Sized Types (ZST). Размер равен нулю, но все еще можно создать экземпляр типа.


К ZST относятся:


  • Пустые структуры
  • Unit-like структуры
  • Пустые кортежи
  • Пустые массивы

!!! Подавать layout таких типов в функции выделения памяти категорически нелья

Ну т.е. можно, но результатом будет undefined behavior.


3. Empty Types. Экзотические типы, экземпляров которых не существует.


Пустой enum:


enum ZeroVariants {}

NeverType (на текущий момент тип не "стабилизирован"):


let x: ! = panic!();

4. Dynamically Sized Types (DSTs). Размер таких типов неизвестен во время компиляции:


  • интерфейсы (traits);
  • срезы (slices): [T], str.

Rust не примет такую запись:


let s1: str = "Hello there!";

Интересный вопрос — почему, ведь можно посчитать размер памяти, которая требуется для "Hello there!"? Есть требование, что все экземпляры типа должны иметь одинаковый размер, вот ему-то значения str и не соответствуют (т.е. единого размера нет), так что — &str.


Далее, если интересно, см.:


  • The Rustonomicon::Exotically Sized Types
  • The Rust Reference::Type Layout

Запись / чтение


Теперь у нас все готово для того, чтобы отправлять переменные в Сумрак и выводить обратно.


Туда:


    let ppoint = alloc_t::<Point>();

    // Write to heap
    {
        let p = Point{x: 101, y:201};
        unsafe {ppoint.write(p)}
        println!("ppoint.write() completed");
    }

  • Важно: Деструктор для p при этом НЕ вызывается, т.е. Rust как бы "забывает" про эту переменную

Обратно:


    // Read from heap
    {
        let p;
        unsafe { p = ppoint.read()}
        println!("ppoint.read() completed: {:?}", p);
    }

Для того чтобы посмотреть, когда же вызывается деструктор, реализуем Drop для Point:


impl Drop for Point {
    fn drop(&mut self) {
        println!("Point dropped: {:?}", self);
    }
}

Все вместе при запуске дает результат:


ppoint.write() completed
ppoint.read() completed: Point { x: 101, y: 201 }
Point dropped: Point { x: 101, y: 201 }

Т.е. сначала записываем, затем читаем, и только потом вызывается деструктор у прочитанного значения.


Еще немного — и с кучей завершим.

Источник: https://habr.com/ru/post/573422/


Интересные статьи

Интересные статьи

Всем привет! Это будет очень маленькая статья. Наша задача тут: подключиться к локальному серверу FTP (я выбрала FileZilla) и отправить туда чего-нибудь используя (очевидно) FTP протокол....
Команда Rust рада сообщить о выпуске новой версии, 1.43.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение. Если вы установили пред...
Первый прототип солнечного сервера с контроллером заряда. Фото: solar.lowtechmagazine.com В сентябре 2018 году энтузиаст из Low-tech Magazine запустил проект «низкотехнологичного» веб-сервер...
Регулярно бывая на сайтах фриланса в обеих ипостасях — как исполнителя, так и заказчика, я часто встречаю повторяющиеся мотивы в описании многих заданий, типа задач будет много «агентства и студи...
Во многих мобильных играх есть внутренняя валюта или что-то полезное, что можно получить после просмотра рекламы. Первое время я смотрел такую рекламу и даже находил что-то интересное в ней, но ч...