Can a 'static Arc-based RwLock Guard be Sound?

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() }
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.