
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
возвращает нужный нам элемента по индексу, учитывая, что последовательность по факту состоит из двух слайсов.impl BorrowMut<VecDeque<T>>
. Теперь я понимаю, что это несколько неосмотрительно: код в unsafe полагается на то, что адрес, по которому лежат данные, не меняется. В случае impl BorrowMut
такой гарантии уже нет. Для доказательства того, что подобный вариант является unsound, предположим, что MutPair уже реализовал подобный функционал (это изменение я, с вашего позволения, опущу, потому что это чисто механическое изменение кода) и рассмотрим следующий тип:#[derive(Default)]
struct DeqWrapper<T> {
inner: VecDeque<T>,
}
Для него можно написать следующие реализации Borrow
и BorrowMut
:impl<T> Borrow<VecDeque<T>> for DeqWrapper<T> {
fn borrow(&self) -> &VecDeque<T> {
&self.inner
}
}
impl<T> BorrowMut<VecDeque<T>> for DeqWrapper<T> {
fn borrow_mut(&mut self) -> &mut VecDeque<T> {
self.inner.clear();
&mut self.inner
}
}
Да, это отвратительно, да, так никто (я надеюсь) не пишет. Но принципиально нас ничего от этого не ограждает. Хочу отметить, что код выше не содержит unsafe
.let wrapper = DeqWrapper::<i32>::default();
wrapper.inner.push_back(0);
wrapper.inner.push_back(1);
let mut mut_pair = MutPair::new(wrapper).unwrap(); // (1)
let _ = mut_pair.get(); // (2)
Что тут происходит? В (1) мы делаем внутри MutPair::new
проверку длины дека. Так как при этом мы используем .borrow()
, DeqWrapper
даёт ссылку на поле inner
, длина которого как раз достаточна, поэтому MutPair
возвращает Ok
и .unwrap()
не паникует. В (2) мы вызываем .borrow_mut()
, из дека удаляется данные, MutPair::get
использует get_mut
, предполагая, что данные есть, и — вуаля! — мы получаем ссылку на неинициализированные данные! Что является UB в Rust.impl BorrowMut<VecDeque<T>>
. Теперь я понимаю, что это несколько неосмотрительно: код в unsafe полагается на то, что адрес, по которому лежат данные, не меняется. В случае impl BorrowMut
такой гарантии уже нет. Для доказательства того, что подобный вариант является unsound, предположим, что MutPair уже реализовал подобный функционал (это изменение я, с вашего позволения, опущу, потому что это чисто механическое изменение кода) и рассмотрим следующий тип:#[derive(Default)]
struct DeqWrapper<T> {
inner: VecDeque<T>,
}
Для него можно написать следующие реализации Borrow
и BorrowMut
:impl<T> Borrow<VecDeque<T>> for DeqWrapper<T> {
fn borrow(&self) -> &VecDeque<T> {
&self.inner
}
}
impl<T> BorrowMut<VecDeque<T>> for DeqWrapper<T> {
fn borrow_mut(&mut self) -> &mut VecDeque<T> {
self.inner.clear();
&mut self.inner
}
}
Да, это отвратительно, да, так никто (я надеюсь) не пишет. Но принципиально нас ничего от этого не ограждает. Хочу отметить, что код выше не содержит unsafe
.let wrapper = DeqWrapper::<i32>::default();
wrapper.inner.push_back(0);
wrapper.inner.push_back(1);
let mut mut_pair = MutPair::new(wrapper).unwrap(); // (1)
let _ = mut_pair.get(); // (2)
Что тут происходит? В (1) мы делаем внутри MutPair::new
проверку длины дека. Так как при этом мы используем .borrow()
, DeqWrapper
даёт ссылку на поле inner
, длина которого как раз достаточна, поэтому MutPair
возвращает Ok
и .unwrap()
не паникует. В (2) мы вызываем .borrow_mut()
, из дека удаляется данные, MutPair::get
использует get_mut
, предполагая, что данные есть, и — вуаля! — мы получаем ссылку на неинициализированные данные! Что является UB в Rust.BorrowMut
, чего делать не стоит (unsafe не должен доверять safe).MutPair
как unsafe
. В принципе, вариант, но это немного лишает смысла эту абстракцию.VecDeque<T>
и &mut VecDeque<T>
). Тоже рабочий вариант (если я таки буду публиковать этот код на crates.io, то, вероятно, так и поступил бы), но это выглядит несколько избыточным решением + страдает эргономика: пользователю теперь надо импортировать нужный трейт, который он(а) не может реализовать.VecDeque
, но и требует доказательство вменяемости такой реализации. К сожалению, этот вариант сильно превосходит текущие возможности Rust и навряд ли когда-то будет осуществлён.