Сорцы на выходные на «расте»

от автора

Наибольший кайф античных книг в их выдержке, актуальности. Книги точно проверены временем, актуальны (правда если знаешь в чем) и как ни странно, честны (книги хотели бы, чтобы их читали через сотню или тысячу лет). В программировании античные скрипты, наверно, написал Деннис Ритчи, сегодня же почитаем современников — Sean Parent (довольно известный чел в c++ тусовке) начал писать на расте (возможно, как начал так и закончил, но мы живем в моменте — поэтому предлагаю насладиться). Читать на расте сложнее, чем писать (это прям факт) — пишут его двое (автор и компилятор), а читают, ну читают на гитхабе. Далее в прозаическом сочинении свободной композиции, подразумевающем впечатления и соображения автора по конкретному поводу или предмету, рассмотрим компоненты новой библиотеки и попробуем насладиться примерами кода.

Начнем, пожалуй, с простого алгоритма (так сказать, алгоритмы тоже бывают в одну строчку, а не то, что обычно пишут на модных майках):

const fn align_index(align: usize, index: usize) -> usize {     debug_assert!(align.is_power_of_two());     (index + align - 1) & !(align - 1) }  #[cfg(test)] mod tests {     use super::*;      #[test]     fn test_align_index() {         assert_eq!(align_index(16, 0), 0);         assert_eq!(align_index(16, 1), 16);         assert_eq!(align_index(16, 15), 16);         assert_eq!(align_index(16, 16), 16);         assert_eq!(align_index(16, 17), 32);     } } 

Как можно заметить, тесты прекрасно дополняют читаемость функции и, как ни странно, располагаются всего двумя строчками ниже.

Могут возникнуть вопросы о выравнивании индекса, собственно, зачем оно? Вопросы и гугление может привести к интересной связи между «alignment» и «memory paging» (спасибо Алисе за перевод — «страничное запоминание»). Тут настолько гуглить, на сколько хватит терпения и врожденного интереса.

Собственно, алгоритм выше мы увидим в следующих структурах данных:

/// A sequence that stores heterogeneous values with proper alignment. /// /// The `RawSequence` provides a memory-efficient way to store heterogeneous values /// while maintaining proper alignment requirements for each type... pub struct RawSequence {     buffer: RawVec, }  ...  #[test] fn sequence_operations() {     let mut seq = RawSequence::new();      seq.push(100u32);     seq.push(200u32);     seq.push(42.0f64);     seq.push("Hello, world!");     ... }  

RawVec это почти равно Vec<MaybeUninit<u8>>, то есть тупо храним байтики, что позволяет нам хранить в векторе гетерогенные (вот это слово) составляющие. Для статических языков программирования эт прям очень круто. Далее пару методов структурки:

/// ... /// Returns a tuple containing: /// - A reference to the value /// - The position immediately after the value #[must_use] pub unsafe fn next<T>(&self, p: usize) -> (&T, usize) {     let aligned: usize = align_index(mem::align_of::<T>(), p);     let ptr = unsafe { self.buffer.as_ptr().add(aligned).cast::<T>() };     unsafe { (&*ptr, aligned + mem::size_of::<T>()) } }     pub fn push<T>(&mut self, value: T) {     let len = self.buffer.len();     let aligned: usize = align_index(mem::align_of::<T>(), len);     let new_len = aligned + mem::size_of::<T>();      self.buffer.reserve(new_len - len);     unsafe {         self.buffer.set_len(new_len);         std::ptr::write(self.buffer.as_mut_ptr().add(aligned).cast::<T>(), value);     } } 

Второй метод, который «пуш» — немного длиннее, как мне кажется, чем нужно. Пару моментов, которые не совсем очевидны: 1) buffer.reserve принимает дополнительное кол-во элементов, 2) buffer.set_len прям нужно (не забыть) вызвать.

Перейдем к другой структурке (там их много, и из них как из компонентов собирается конечный механизм, но пожалуй, остальные не очень интересны):

/// A list using a guaranteed memory layout (`repr(C)`), with tail stored first so appending items /// does not change the memory layout of prior items. #[repr(C)] #[derive(Clone)] pub struct CStackList<H, T>(pub T, pub H);  impl<H: 'static, T: List> List for CStackList<H, T> { type Push<U: 'static> = CStackList<U, Self>; fn push<U: 'static>(self, item: U) -> Self::Push<U> {     CStackList(self, item) }  type Append<U: List> = <T::Append<U> as List>::Push<H>; fn append<U: List>(self, other: U) -> Self::Append<U> {     self.0.append(other).push(self.1) }  type ReverseOnto<U: List> = T::ReverseOnto<U::Push<H>>; fn reverse_onto<U: List>(self, other: U) -> Self::ReverseOnto<U> {     self.0.reverse_onto(other.push(self.1)) } ... } 

Прекрасные рекурсивные алгоритмы — не только код, но и определение типа. Пожалуй, только из-за рекурсии уже можно идти в программирование (на счет указателей, конечно, есть вопросы, но старина Спольски, по-моему, сейчас уже не ведет блоги). С рекурсий на типах еще не все — какие-то ребята сделали библиотеку typenum и теперь вроде индекс можно проверять на этапе компиляции (упоролись они, конечно, знатно, с другой стороны, почему бы и не проверять индекс, полезно вроде):

impl<H: 'static, T: List> ListIndex<RangeFrom<U0>> for CStackList<H, T> {     type Output = CStackList<H, T>;     fn index(&self, _index: RangeFrom<U0>) -> &Self::Output {         self     } }  impl<H: 'static, T: List, U: Unsigned, B: Bit> ListIndex<RangeFrom<UInt<U, B>>> for CStackList<H, T> where     T: ListIndex<RangeFrom<Sub1<UInt<U, B>>>>,     UInt<U, B>: Sub<B1>, {     type Output = <T as ListIndex<RangeFrom<Sub1<UInt<U, B>>>>>::Output;     fn index(&self, index: RangeFrom<UInt<U, B>>) -> &Self::Output {         self.tail().index((index.start - B1)..)     } } 

Там Sean Parent написал еще много интересных компонентов (надеюсь, любознательный читатель нагуглит репозиторий), из которых потом выйдет вот такое описание репозитория (надеюсь, Шон сделает какой-нибудь супер видос про библиотеку, и все с открытыми ртами скажут: «а что, так можно было?», но пока ждем):

// cel-rs provides a stack-based runtime for developing domain specific languages, including // concative languages to describe concurrent processes. A sequence is a list of operations (the // machine instructions). Each operation is a closure that takes it's arguments from the stack and // the result is pushed back onto the stack.  // Create a segment that takes a u32 and &str as arguments let segment = Segment::<(u32, &str)>::new()     .op1r(|s| {         let r = s.parse::<u32>()?;         Ok(r)     })     .op2(|a, b| a + b)     .op1(|r| r.to_string()); assert_eq!(segment.call((1u32, "2")).unwrap(), "3"); 


ссылка на оригинал статьи https://habr.com/ru/articles/926032/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *