Hi there!
I have an interface which helps me interact with a list in a foreign language. I want to create an idiomatic binding to this in rust, or at least try.
The thing is, is that since I cannot hand out references to items in this foreign list, my options are to either copy the list, hand out references to that, and then update it, or to "cache" acquired items.
I want to do the latter, so I have the following idea:
pub struct List<T> {
// Implementation details like a handle, etc.
}
impl<T: ListItem> List<T> {
//We want to avoid calling this multiple times
//for the same item since we can do better than
//that.
pub fn get_at(&self, idx: usize) -> T {
//
}
pub fn get_view(&'_ self) -> ListView<'_, T> {
//
}
pub fn set_at(&mut self, idx: usize) -> T {
//
}
pub fn get_view_mut(&'_ mut self) -> ListViewMut<'_, T> {
//
}
}
pub struct ListView<'a, T: ListItem> {
list: &'a List<T>,
len: usize, // Used in the construction of this,
// and to facilitate panics on rust's
// side instead of over an ffi.
cached_items: Vec<UnsafeCell<Option<T>>>,
}
impl<'a, T: ListItem> Index<usize> for ListView<'a, T> {
type Output = T;
fn index(&self, idx: usize) -> &T {
//SAFETY:
// Make sure that the list's items are never changed after
// handing out a reference to them. Since references are
// only ever handed out when there is a `Some` variant in
// self.cached_items[idx], there is always an item there,
// and the values at those locations are only ever modified
// when the spot is populated by a `None`.
unsafe {
let item = &self.cached_items[idx];
let item = item.get();
if (*item).is_none() {
*item = Some(self.list.get_at(idx));
}
(*(item as *const Option<T>)).as_ref().unwrap()
}
}
}
enum Item<T> {
Read(T),
PossiblyModified(T),
}
impl<T> Item<T> {
fn get_ref(&self) -> &T {
match self { /**/ }
}
fn make_mut(&mut self) -> &mut T {
match self {
Item::Read(x) => {
/* Go about swapping self to be Item::PossiblyModified */
},
Item::PossiblyModified(x) => x,
}
}
}
pub struct ListViewMut<'a, T: ListItem> {
list: &'a mut List<T>,
len: usize,
cached_items: Vec<UnsafeCell<Option<Item<T>>>>,
}
impl<'a, T: ListItem> Index<usize> for ListViewMut<'a, T> {
type Output = T;
fn index(&self, idx: usize) -> &T {
//SAFETY:
// Since rust prevents us from indexing mutably _and_
// [im]mutably at the same time, we don't have to worry about
// overwriting a preexisting entry.
// For more, please see `<ListView<'a, T> as Index<usize>>::index`'s
// unsafety note.
unsafe {
let item = &self.cached_items[idx];
let item = item.get();
if (*item).is_none() {
*item = Some(Item::Read(self.list.get_at(idx)));
}
(*(item as *const Option<Item<T>>)).as_ref().unwrap().get_ref()
}
}
}
impl<'a, T: ListItem> IndexMut<usize> for ListViewMut<'a, T> {
fn index_mut(&self, idx: usize) -> &mut T {
//SAFETY:
// Make sure that the list's item states are never changed after
// handing out a mutable reference to them. Since the references
// have a lifetime attached to them, it is effectively impossible
// for this to be called in a potentially conflicting way.
// For more: See `<ListView<'a, T> as Index<usize>>::index`'s
// unsafety note.
unsafe {
let item = &self.cached_items[idx];
let item = item.get();
if (*item).is_none() {
*item = Some(Item::PossiblyModified(self.list.get_at(idx)));
}
(*item).as_mut().unwrap().make_mut()
}
}
}
I apologize for the wall of code, but in reality the important stuff is marked under a SAFETY
comment.
I am 90% sure that this is fine, but would like to make sure that it is.
Thanks!