
Size: a a a
buffers.push_back(input_buffer);
input_buffer = output_buffer;
output_buffer = buffers.pop_front().unwrap();
unwrap
здесь совершенно лишний: после того, как в дек положили элемент, в нём обязательно есть как минимум один элемент, поэтому pop_front
заведомо вернёт Some(_)
.std::collections::VecDeque
обеспечивает доступ по индексам, но моей проблемы это бы не решает, так как таким образом можно получить только доступ к отдельным элементам, но не к, скажем, внутреннему слайсу, а взять две мутабельные ссылки одновременно даже по двум заведомо разным индексам мне не дал бы borrow checker.
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
возвращает нужный нам элемента по индексу, учитывая, что последовательность по факту состоит из двух слайсов.incremented
фактически возвращает (self.first + 1) % self.inner.len()
, только более работает немного более эффективно).MutPair::new
проверяет, что дек содержит как минимум два элемента, поэтому ссылки, возвращаемые get
, гарантированно не алиасятся.&mut
от значения, полученного путём разыменовывания сырого указателя, полученная ссылка имеет lifetime, на который не накладывается никаких ограничений. Фактический lifetime приписывается исходя из вывода типов. Я не писал явных lifetime-ов, поэтому за счёт lifetime elision полученные ссылки имеют тот же lifetime, что и self
. Borrow checker гарантирует нам, что, пока возвращённые ссылки используются, get
нельзя вызвать снова, поэтому размножить мутабельные ссылки и заалиасить их не получится.get
довольно существенным образом зависит от проверки в new
, то есть от кода, напрямую не связанного с get
. В данном случае уследить за условиями было несложно, но в каком-то более сложно устроенном коде это было бы не так просто заметить. Отличная иллюстрация того, что unsafe может протекать.std::mem::transmute
для того, чтобы принудительно перезаписать lifetime, а ссылки брались через get_unchecked_mut
. Этот вариант давал ошибку в miri. Я не был уверен, кто был в данном случае неправ, поэтому задал соответствующий вопрос в официальном Rust чате в Discord. Очень хороший человек @udoprog потратил время на то, чтобы разобраться в моём коде и указать мне на то, что я действительно провоцирую подобным образом UB (так что miri таки был прав), и предложил использовать сырые указатели. Спасибо, @udoprog