Hi, I am creating a component to hold and allocate data in a contiguous memory array, kinda like an arena allocator for single typed data with some additional features.
The main idea is that memory is pre-allocated and other components can just overwrite this memory block and get back a reference counted pointer for it (Similar to Arc).
I currently have all this working, having implemented the allocator and also an Arc structure (because i didn't find a way to use Arc without allocating memory). Although i am a bit concerned about lifetimes of the pointers, as the pointer can never outlive the underlaying memory they are pointing to.
Below is a stripped down version of what i have, that is mostly based on the implementation of Arc.
struct Element<'a, T: ?Sized + Default> {
ref_count: AtomicUsize,
el: T,
mark: PhantomData<&'a ()>
}
impl<'a, T: ?Sized + Default> Element<'a, T>{
pub fn set(&mut self,val: T) -> ElementAtomicRef<'a,T>{
let res = self.ref_count.load(Ordering::Relaxed);
if res == 0{
self.el = val;
}else{
panic!("This element cannot be set, as there are still references to it");
}
return ElementAtomicRef::new(self);
}
}
pub struct ElementAtomicRef<'a, T: ?Sized + Default>{
ptr: NonNull<Element<'a, T>>
}
impl<'a, T: ?Sized + Default> ElementAtomicRef<'a, T>{
fn new(el : &mut Element<'a, T>) -> ElementAtomicRef<'a, T>{
let old_rc = el.ref_count.fetch_add(1, Ordering::Relaxed);
if old_rc >= isize::MAX as usize {
std::process::abort();
}
ElementAtomicRef{
ptr: NonNull::new(el as *mut _).unwrap()
}
}
}
impl<'a, T: ?Sized + Default> Clone for ElementAtomicRef<'a,T> {
fn clone(&self) -> ElementAtomicRef<'a,T> {
let inner = unsafe { self.ptr.as_ref() };
let old_rc = inner.ref_count.fetch_add(1, Ordering::Relaxed);
if old_rc >= isize::MAX as usize {
std::process::abort();
}
Self {
ptr: self.ptr
}
}
}
pub struct DatabaseMemory<'a, T: ?Sized + Default>{
object_memory: [Element<'a, T>]>
pub fn add(&self, element: T) -> Option<ElementAtomicRef<'a, T>>{
let idx; // Get available index from some place
if let Some(idx) = idx{
let memory_element = self.object_memory[idx as usize];
Some(memory_element.set(element))
}else{
None
}
}
}
Currently, i have the lifetime of the atomic references/pointers tied to the underlaying structure that holds the data, but I am afraid this might not be enough as the memory could possibly get dropped before the pointer. My questions are:
- Is my assumption correct and the current implementation can lead to undefined behavior, due dangling references?
- Is there a way to ensure the lifetime of the element always outlives the pointer? (I read about subtyping and variance, even tried to implement it without success) If so, how can I do it?
In the case where the problem exists and cannot be solved with lifetimes the only possible way that I can think of is to use the static lifetime for the underlaying memory structure.