
Size: a a a
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.MutPair
имеет следующий вид: pub struct Original;
pub struct View;
pub struct MutPair<T, Deq = VecDeque<T>, Idx = usize, Kind = Original> {
inner: Deq,
first: Idx,
_type: PhantomData<(T, Kind)>,
}
Такое замысловатое определение позволяет расширить возможности, предоставляемые API.BorrowMut
? Очень просто: публичные методы дают возможность создать только MutPair
с Deq
, являющимся VecDeque<T>
и &mut VecDeque<T>
, поэтому все сложности, связанные с нестабильностью адресов слайсов, отпадают.usize
? Дело в том, что у владеющего MutPair
теперь есть два метода, которые создают MutPair
, держащие ссылку на дек. Один из них (создаваемый методом view
) работает, как ожидается, а второй, создаваемый методом linked_view
, при вызове методов step_right
/step_left
также меняет позицию изначального MutPair
. Для того, чтобы поддержать этот случай использования, имеется возможность использовать как usize
, так и &mut usize
.Kind
? Дело в том, что, как я уже подчёркивал, memory safety данного кода существенным образом опирается на невозможность поменять длину дека. Как для владеющего, так и для заимствующего MutPair
имеет смысл операция into_inner
, которая отдаёт дек, но безусловное разрешение подобного метода даёт возможность написать некорректный код с использованием безопасного API (я, к своему стыду, изначально допустил эту ошибку):let deque = ...;
let mut owning_pair = MutPair::try_from(deque).unwrap();
let mut pair = owning_pair.view();
let mut deque = view.into_inner();
deque.clear();
let _ = owning_pair.get(); // Hello UB!
Таким образом, into_inner
для заимствующего MutPair
безопасен только при условии, что объект построен из ссылки на дек непосредственно, а не получен из владеющего MutPair
. Именно это ограничение и обеспечивается четвёртым ти́повым параметром: методы view
/linked_view
возвращают MutPair
с Kind
= View
, в то время как метод into_inner
определён лишь для MutPair
с Kind
= Original
.MutPair
имеет следующий вид: pub struct Original;
pub struct View;
pub struct MutPair<T, Deq = VecDeque<T>, Idx = usize, Kind = Original> {
inner: Deq,
first: Idx,
_type: PhantomData<(T, Kind)>,
}
Такое замысловатое определение позволяет расширить возможности, предоставляемые API.BorrowMut
? Очень просто: публичные методы дают возможность создать только MutPair
с Deq
, являющимся VecDeque<T>
и &mut VecDeque<T>
, поэтому все сложности, связанные с нестабильностью адресов слайсов, отпадают.usize
? Дело в том, что у владеющего MutPair
теперь есть два метода, которые создают MutPair
, держащие ссылку на дек. Один из них (создаваемый методом view
) работает, как ожидается, а второй, создаваемый методом linked_view
, при вызове методов step_right
/step_left
также меняет позицию изначального MutPair
. Для того, чтобы поддержать этот случай использования, имеется возможность использовать как usize
, так и &mut usize
.Kind
? Дело в том, что, как я уже подчёркивал, memory safety данного кода существенным образом опирается на невозможность поменять длину дека. Как для владеющего, так и для заимствующего MutPair
имеет смысл операция into_inner
, которая отдаёт дек, но безусловное разрешение подобного метода даёт возможность написать некорректный код с использованием безопасного API (я, к своему стыду, изначально допустил эту ошибку):let deque = ...;
let mut owning_pair = MutPair::try_from(deque).unwrap();
let mut pair = owning_pair.view();
let mut deque = view.into_inner();
deque.clear();
let _ = owning_pair.get(); // Hello UB!
Таким образом, into_inner
для заимствующего MutPair
безопасен только при условии, что объект построен из ссылки на дек непосредственно, а не получен из владеющего MutPair
. Именно это ограничение и обеспечивается четвёртым ти́повым параметром: методы view
/linked_view
возвращают MutPair
с Kind
= View
, в то время как метод into_inner
определён лишь для MutPair
с Kind
= Original
.VecDeque
адаптер, гарантирующий стабильные адреса. Я думал о том, чтобы написать такой адаптер самому, но потом понял, что с нормальными гарантиями такой адаптер может написать только автор кода VecDeque
, который знает внутреннее устройство и может точно сказать, какие методы не перемещают данные — собственно, в этом смысле сейчас безопасность кода базируется на допущениях, который верны сейчас, но в силу отсутствия явных контрактов могут стать неверными в будущих релизах std