osom_lib_arc 0.1.0

ABI-stable atomic reference counted pointers for osom_lib.
Documentation
//! Holds the definition of [`CArc`].
use core::{
    borrow::Borrow,
    sync::atomic::{Ordering, fence},
};

use osom_lib_alloc::traits::Allocator;
use osom_lib_reprc::traits::ReprC;

use super::{errors::CArcError, internal::InternalArc, weak::CWeak};

/// A smart pointer that can be used to share ownership of a value.
///
/// This struct is equivalent to the standard `Arc` type,
/// but is `#[repr(C)]` and thus safe to use across the ffi boundaries.
#[repr(transparent)]
#[must_use]
#[derive(Debug)]
pub struct CArc<T, TAllocator: Allocator> {
    internal: InternalArc<T, TAllocator>,
}

unsafe impl<T: ReprC, TAllocator: Allocator> ReprC for CArc<T, TAllocator> {
    const CHECK: () = {
        let () = <InternalArc<T, TAllocator> as ReprC>::CHECK;
        let () = T::CHECK;
        let () = TAllocator::CHECK;
    };
}

/// The result of abandoning a [`CArc`]. Holds the underlying data and
/// a weak reference to the [`CArc`].
#[must_use]
#[repr(C)]
#[derive(Debug)]
pub struct AbandonResult<T, TAllocator: Allocator> {
    /// The underlying data.
    pub data: T,

    /// The weak reference to the final strong [`CArc`] reference.
    pub weak: CWeak<T, TAllocator>,
}

unsafe impl<T: ReprC, TAllocator: Allocator> ReprC for AbandonResult<T, TAllocator> {
    const CHECK: () = {
        let () = <CWeak<T, TAllocator> as ReprC>::CHECK;
        let () = T::CHECK;
        let () = TAllocator::CHECK;
    };
}

impl<T, TAllocator: Allocator> CArc<T, TAllocator> {
    /// Creates a new [`CArc`] with the default allocator.
    ///
    /// # Errors
    ///
    /// For details see [`CArcError`].
    #[inline]
    pub fn new(value: T) -> Result<Self, CArcError> {
        let internal = InternalArc::new(value)?;
        Ok(Self::from_internal(internal))
    }

    /// Creates a new [`CArc`] with a custom allocator.
    ///
    /// # Errors
    ///
    /// For details see [`CArcError`].
    #[inline]
    pub fn with_allocator(value: T, allocator: TAllocator) -> Result<Self, CArcError> {
        let internal = InternalArc::with_allocator(value, allocator)?;
        Ok(Self::from_internal(internal))
    }

    /// Returns the number of strong references to the [`CArc`].
    #[inline(always)]
    #[must_use]
    pub fn strong_count(&self) -> u32 {
        self.internal.strong().load(Ordering::Relaxed)
    }

    /// Returns the number of weak references to the [`CArc`].
    #[inline(always)]
    #[must_use]
    pub fn weak_count(&self) -> u32 {
        self.internal.weak().load(Ordering::Relaxed)
    }

    /// Returns a reference to the underlying value.
    #[inline]
    #[must_use]
    pub const fn data(&self) -> &T {
        self.internal.data()
    }

    /// Creates a new [`CWeak`] reference to the [`CArc`].
    pub fn downgrade(&self) -> CWeak<T, TAllocator> {
        let internal_clone = self.internal.clone();
        internal_clone.weak().fetch_add(1, Ordering::Relaxed);
        CWeak::from_internal(internal_clone)
    }

    /// Abandons current [`CArc`].
    ///
    /// This function returns `None` if the underlying strong reference counter
    /// is still positive. Otherwise it returns the underlying data and
    /// a weak reference to the [`CArc`]. In particular the data will be dropped
    /// by the caller if that was the last strong reference.
    #[inline]
    #[must_use]
    pub fn abandon(mut self) -> Option<AbandonResult<T, TAllocator>> {
        let result = unsafe { self.internal_abandon() };
        core::mem::forget(self);
        result
    }

    #[inline(always)]
    pub(super) fn from_internal(internal: InternalArc<T, TAllocator>) -> Self {
        Self { internal }
    }

    unsafe fn internal_abandon(&mut self) -> Option<AbandonResult<T, TAllocator>> {
        let internal = unsafe { core::ptr::read(&raw const self.internal) };
        let prev = internal.strong().fetch_sub(1, Ordering::Release);
        if prev > 1 {
            return None;
        }

        let data = unsafe { internal.read_data() };

        // Synchronize with all prior Release decrements before deallocating.
        fence(Ordering::Acquire);

        Some(AbandonResult {
            data,
            weak: CWeak::from_internal(internal),
        })
    }
}

impl<T, TAllocator: Allocator> Drop for CArc<T, TAllocator> {
    fn drop(&mut self) {
        let _ = unsafe { self.internal_abandon() };
    }
}

impl<T, TAllocator: Allocator> AsRef<T> for CArc<T, TAllocator> {
    fn as_ref(&self) -> &T {
        self.data()
    }
}

impl<T, TAllocator: Allocator> Borrow<T> for CArc<T, TAllocator> {
    fn borrow(&self) -> &T {
        self.data()
    }
}

impl<T, TAllocator: Allocator> Clone for CArc<T, TAllocator> {
    fn clone(&self) -> Self {
        let internal_clone = self.internal.clone();
        internal_clone.strong().fetch_add(1, Ordering::Relaxed);
        Self {
            internal: internal_clone,
        }
    }
}