I'm using the atomicell
crate to provide RwLock
-like functionality for my ECS.
Now I'm integrating scripting with the ECS, and I need to be able to pass 'static
read or write guards into the scripting environment. I believe this can be done soundly because I'm using an Arc<AtomicCell>
to store the data, so as long as I store a clone of the Arc
along with the borrow lock, then the AtomicCell
is sure to outlive the borrow.
I know there are very possibly nuances I'm not thinking of here, so I wanted to know whether or not this looks like it should be sound, or at least whether or not the idea is sound/possible.
use std::ptr::NonNull;
use bones_framework::prelude::borrow::{AtomicBorrow, AtomicBorrowMut};
use crate::prelude::*;
/// Extension trait over [`Arc<AtomicCell>`] to allow obtaining a `'static` borrow guard.
pub trait ArcAtomicCellExt<T> {
fn lock_static(self) -> ArcRef<T>;
fn lock_static_mut(self) -> ArcRefMut<T>;
}
impl<T> ArcAtomicCellExt<T> for Arc<AtomicCell<T>> {
fn lock_static(self) -> ArcRef<T> {
let reflock = self.borrow();
let (ptr, borrow) = Ref::into_split(reflock);
// SOUND: This transmutes the lifetime of the atomic borrow to 'static, so that we can
// store it without the lifetime. This is sound because it's reference refers to the arc
// that we are storing along with it. As long as we drop the borrow before we drop the
// arc, we should be fine.
// TODO: get some review to make sure that's correct.
let borrow =
unsafe { std::mem::transmute::<AtomicBorrow<'_>, AtomicBorrow<'static>>(borrow) };
ArcRef {
arc: self,
ptr,
borrow,
}
}
fn lock_static_mut(self) -> ArcRefMut<T> {
let reflock = self.borrow_mut();
let (ptr, borrow) = RefMut::into_split(reflock);
// SOUND: This transmutes the lifetime of the atomic borrow to 'static, so that we can
// store it without the lifetime. This is sound because it's reference refers to the arc
// that we are storing along with it. As long as we drop the borrow before we drop the
// arc, we should be fine.
// TODO: get some review to make sure that's correct.
let borrow =
unsafe { std::mem::transmute::<AtomicBorrowMut<'_>, AtomicBorrowMut<'static>>(borrow) };
ArcRefMut {
arc: self,
ptr,
borrow,
}
}
}
/// A borrow lock on an [`Arc<AtomicCell>`].
pub struct ArcRef<T: ?Sized> {
arc: Arc<AtomicCell<T>>,
ptr: NonNull<T>,
borrow: AtomicBorrow<'static>,
}
unsafe impl<T: Sync + Send + ?Sized> Sync for ArcRef<T> {}
unsafe impl<T: Send + Send + ?Sized> Send for ArcRef<T> {}
impl<T: ?Sized> std::ops::Deref for ArcRef<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
// SOUND: We hold an atomic borrow
unsafe { self.ptr.as_ref() }
}
}
/// A mutable borrow lock on an [`Arc<AtomicCell>`].
pub struct ArcRefMut<T: ?Sized> {
arc: Arc<AtomicCell<T>>,
ptr: NonNull<T>,
borrow: AtomicBorrowMut<'static>,
}
unsafe impl<T: Sync + Send + ?Sized> Sync for ArcRefMut<T> {}
unsafe impl<T: Send + Send + ?Sized> Send for ArcRefMut<T> {}
impl<T: ?Sized> std::ops::Deref for ArcRefMut<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
// SOUND: We hold an atomic borrow
unsafe { self.ptr.as_ref() }
}
}
impl<T: ?Sized> std::ops::DerefMut for ArcRefMut<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
// SOUND: we hold an atomic borrow
unsafe { self.ptr.as_mut() }
}
}