Size: a a a

2019 December 30
Блог*
#meme
источник
Блог*
источник
Блог*
#prog #rust #моё
Во время решения одной из задач Advent of Code мне пришлось решать довольно заковыристую в плане владения задачу: требовалось связать цепочку интерпретаторов так, чтобы данные, выдаваемые одним экземпляром интерпретатора, использовались в качестве входных данных следующего интерпретатора.

Дополнительной сложности придал тот факт, что цепочка замкнута в петлю, поэтому просто пройтись по цепочке один раз было недостаточно. Здесь напрашиваются сопрограммы, но их в Rust нет, поэтому мне пришлось сделать дек очередей в качестве буферов для данных. В итоге в моём решении были строки вида:

buffers.push_back(input_buffer);
input_buffer = output_buffer;
output_buffer = buffers.pop_front().unwrap();

Мало того, что это выглядит уродливо и многословно, так ещё и unwrap здесь совершенно лишний: после того, как в дек положили элемент, в нём обязательно есть как минимум один элемент, поэтому pop_front заведомо вернёт Some(_).

Что же мне нужно было на самом деле? Кольцевой буфер очередей, которой обеспечивают мутабельный доступ к парам идущих подряд элементов. std::collections::VecDeque обеспечивает доступ по индексам, но  моей проблемы это бы не решает, так как таким образом можно получить только доступ к отдельным элементам, но не к, скажем, внутреннему слайсу, а взять две мутабельные ссылки одновременно даже по двум заведомо разным индексам мне не дал бы borrow checker.

В тот раз я пошёл по пути наименьшего распределения и оставил тот код,  который я показал выше, но потом у меня возникла мысль, что нужного мне поведения можно было бы достичь через обёртку, которая предоставляла бы нужный мне доступ через unsafe-преобразования. Такую я в итоге и написал: https://gist.github.com/AnthonyMikh/bb41179b20938ba329c3d411ddc1c679.  Вот как она выглядит:

struct MutPair<T> {
   inner: VecDeque<T>,
   first: usize,
}

Здесь хранится только один из индексов пары, потому что второй всегда можно подсчитать, зная его и длину inner. Основной метод этого типа - get, который возвращает две мутабельные ссылки, на элемент с текущим индексом и следующим за ним:

pub fn get(&mut self) -> (&mut T, &mut T) {
   let first = self.first;
   let second = self.incremented();
   let (head, tail, head_len) = {
       let (head, tail) = self.inner.as_mut_slices();
       let len = head.len();
       (head.as_mut_ptr(), tail.as_mut_ptr(), len)
   };

   let first = unsafe { &mut *get_mut(head, tail, head_len, first) };
   let second = unsafe { &mut *get_mut(head, tail, head_len, second) };

   (first, second)
}

Здесь as_mut_slices возвращает мутабельные ссылки на содержимое дека (слайсов два, потому что логически единая последовательность элементов может начинаться перед концом внутреннего вектора и продолжаться от его конца). Borrow checker не даёт нам в лоб взять две мутабельные ссылки, поэтому мы используем стандартный трюк: кастуем ссылки в сырые указатели, которые компилятором уже не отслеживаются, сдвигаем их и преобразуем обратно в ссылки. Вспомогательная функция get_mut возвращает нужный нам элемента по индексу, учитывая, что последовательность по факту состоит из двух слайсов.

Считаю ли я итоговый код завершённым? Почти: я бы хотел хранить impl BorrowMut<VecDeque<T>>, чтобы MutPair можно было использовать тогда, когда невозможно отдать владение деком.
источник
Блог*
Что же делает эту функцию корректной (sound)? Так как компилятор нам тут не помогает, ответственность за ненарушение правил языка ложится на нас.

1. Вычисление индексов гарантирует, что нужный нам элемент действительно лежит в деке (собственно, incremented фактически возвращает (self.first + 1) % self.inner.len(), только более работает немного более эффективно).

2. MutPair::new проверяет, что дек содержит как минимум два элемента, поэтому ссылки, возвращаемые get, гарантированно не алиасятся.

3. Когда берётся &mut от значения, полученного путём разыменовывания сырого указателя, полученная ссылка имеет lifetime, на который не накладывается никаких ограничений. Фактический lifetime приписывается исходя из вывода типов. Я не писал явных lifetime-ов, поэтому за счёт lifetime elision полученные ссылки имеют тот же lifetime, что и self. Borrow checker гарантирует нам, что, пока возвращённые ссылки используются, get нельзя вызвать снова, поэтому размножить мутабельные ссылки и заалиасить их не получится.
источник
Блог*
А теперь ложка дёгтя:
Корректность get довольно существенным образом зависит от проверки в new, то есть от кода, напрямую не связанного с get. В данном случае уследить за условиями было несложно, но в каком-то более сложно устроенном коде это было бы не так просто заметить. Отличная иллюстрация того, что unsafe может протекать.
Также нам приходится следить за тем, чтобы мы меняли элементы дека, но не сам дек как таковой. В идеале, стоило бы хранить соответствующий адаптер над деком, но такого адаптера (пока?) нет.
источник
Блог*
Несмотря на то, что компилятор не следит за сырыми указателями, есть интерпретатор кода miri, который позволяет проверять корректность кода динамически. Мой первый вариант кода использовал std::mem::transmute для того, чтобы принудительно перезаписать lifetime, а ссылки брались через get_unchecked_mut. Этот вариант давал ошибку в miri. Я не был уверен, кто был в данном случае неправ, поэтому задал соответствующий вопрос в официальном Rust чате в Discord. Очень хороший человек @udoprog потратил время на то, чтобы разобраться в моём коде и указать мне на то, что я действительно провоцирую подобным образом UB (так что miri таки был прав), и предложил использовать сырые указатели. Спасибо, @udoprog
источник
2019 December 31
Блог*
#quotes
источник
Блог*
Боров чекер звучит как работа лесника
источник
Блог*
источник
Блог*
#meme
источник
Блог*
источник
Блог*
#prog #article
Замечательная статья про
устройство JPEG: https://habr.com/ru/post/206264/
источник
Блог*
Собственно, артефакты кодирования в виде квадратов на градиентах понятно откуда берутся. Я тут подумал: а что, если кодировать кусок, принимая во внимание не только данные в нём, но и вокруг него? По идее, это должно уменьшить артефакты. С другой стороны, это может снизить эффективность сжатия.

Почти уверен, что мысль не нова и уже кому-то приходила в голову. Если вы знаете о практической реализации этой идеи - напишите в чат, пожалуйста: @decltype_chat_ptr_t
источник
Блог*
источник
Блог*
С новым годом!
источник
2020 January 01
Блог*
С новым годом, @aspirin_pp
источник
Блог*
#quotes
источник
Блог*
айфоны - это го от мира смартфонов
источник
Блог*
#meme
источник
Блог*
источник