I'd like to implement a data structure that stores values in 2 different orders.
Values are inserted once (in both orders) and cannot be removed without dropping the entire data structure ("append only"). They can still be mutated in place.
The values can be accessed immutably or mutably through either one of the orders and the appropriate functions take an immutable borrow of self
or a mutable borrow of self
, respectively. Mutating a value is applied to the value in both orders.
A simplified version of the code looks something like this:
pub struct TwoOrders<V> {
first_order: Vec<*mut V>,
second_order: Vec<*mut V>,
}
impl<V> TwoOrders<V> {
pub fn new() -> Self {
Self {
first_order: vec![],
second_order: vec![],
}
}
pub fn insert(&mut self, value: V) {
let ptr = Box::into_raw(Box::new(value));
// insert ptr into self.first_order in some position
// insert ptr into self.second_order in another position
}
pub fn get_from_first_order(&self, index: usize) -> Option<&V> {
self.first_order
.get(index)
.map(|v| unsafe { v.as_ref().unwrap() })
}
pub fn get_from_second_order(&self, index: usize) -> Option<&V> {
self.second_order
.get(index)
.map(|v| unsafe { v.as_ref().unwrap() })
}
pub fn get_mut_from_first_order(
&mut self,
index: usize,
) -> Option<&mut V> {
self.first_order
.get_mut(index)
.map(|v| unsafe { v.as_mut().unwrap() })
}
pub fn get_mut_from_second_order(
&mut self,
index: usize,
) -> Option<&mut V> {
self.second_order
.get_mut(index)
.map(|v| unsafe { v.as_mut().unwrap() })
}
}
impl<V> Drop for TwoOrders<V> {
fn drop(&mut self) {
self.first_order.clear();
for value_ptr in self.second_order.drain(..) {
std::mem::drop(unsafe { Box::from_raw(value_ptr) })
}
}
}
My question is: is this actually safe?
I am assuming that since a mutable reference to a value is attained only with a mutable borrow of self
, it means that the borrow checker guarantees that there is only one exclusive reference to this value during its lifetime, so the struct itself is conceptually like a compile-time lock.
The raw pointers are never exposed outside the struct and no function allows any access to the values in a way that breaks the safety requirements of references.
Box::into_raw
stops the values from being automatically dropped, and only one copy of the raw pointers is dropped with a manually implemented Drop
(using Box::from_raw
).
Are my assumptions correct?
Another question is: is there any existing construct in rust (in the standard library or otherwise) that would solve this use case without using raw pointers, but also without having to use a reference counted pointer or locks?