1.83K subscribers
3.24K photos
127 videos
15 files
3.52K links
Блог со звёздочкой.

Много репостов, немножко программирования.

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Блог*
#prog #rust #rustlib #amazingopensource Dtolnay продолжает доставлять https://github.com/dtolnay/itoa https://github.com/dtolnay/dtoa
Кажется, надо #github переименовать в #amazingopensource. Точнее изначальный смысл, который я вкладывал, отражает
Forwarded from Антон
Переименовывать?
Final Results
50%
Да
50%
Нет
Forwarded from The After Times
Forwarded from The After Times
Мне нужна бронзовая статуя русалки!

Linux: «Вот вам бронзовый куб 1x1x1 м и напильник»

Gentoo: «Вот вам медь, олово, доменная печь»

FreeBSD: «Вот вам кирка и каска с фонариком. Шахта с медью — в той стороне»

Ubuntu: «Вот вам статуя русалки»
Пользователь: «Но здесь же только хвост!»
Ubuntu: «Введите, пожалуйста apt-get install туловище русалки, apt-get install голова русалки, apt-get install руки русалки...»

MacOS: «Вот вам красивые голые парни»
Пользователь: «Ух ты! А можно потрогать?»

MacOS: «Заплатите 200 евро и активируйте функцию „Потрогать голых парней“»

Windows: «Нет. Я думаю — Вам определенно нужна чугунная статуя Чапаева...»
#meme
Не программированием единым
Forwarded from LALILULELO
Channel photo updated
Блог*
Channel photo updated
Немного новогоднего настроения
Forwarded from The After Times
#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
Forwarded from Nick
Боров чекер звучит как работа лесника