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

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

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

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

 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) -> ! — «фатальная» функция, из таких не возвращаются

См. также:

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

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.

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

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

Туда:

 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 }

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

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

Читайте так же:

  • Наполнение сайта битриксНаполнение сайта битрикс Bitrix Site Manager может показаться непритязательным, но под этой CMS скрывается фантастический алгоритм. Который повышает производительность этой CMS по сравнению почти со всеми другими существующими CMS-приложениями. Это определенно самая экономичная система управления контентом на […]
  • Как правильно составить контент план для сайтаКак правильно составить контент план для сайта Бесчисленные владельцы малого бизнеса и предприниматели изо всех сил пытаются ответить на вопрос: “Что я должен сказать на своем сайте?” Конечно. Вы можете понять. Что вам нужна базовая страница “О нас” и “свяжитесь с нами”. Но что еще? Как насчет плана содержания вашего сайта? Часть […]
  • Яндекс покупает банк «Акрополь»Яндекс покупает банк «Акрополь» Яндекс заключил соглашение о покупке банка «Акрополь». В результате сделки к компании перейдут лицензии «Акрополя». Включая банковскую универсальную. Наличие лицензий позволит Яндексу развить направление финансовых сервисов и запустить новые продукты для пользователей и партнеров. Сумма […]
  • Во Франции оштрафовали Google на €220 млн за нарушения на рынке онлайн-рекламыВо Франции оштрафовали Google на €220 млн за нарушения на рынке онлайн-рекламы Антимонопольное ведомство Франции (French Competition Authority. FCA) оштрафовало Google на 220 млн евро за злоупотребление доминирующей позицией на рынке онлайн-рекламы. Компанию обязали в течение трех лет внести изменения в то. Как она работает в стране. Об этом говорится в заявлении […]