osom_lib_strings 0.1.5

ABI-stable string types and helpers for osom_lib.
Documentation
use core::sync::atomic::{Ordering, fence};

use osom_lib_alloc::traits::Allocator;
use osom_lib_reprc::macros::reprc;
use osom_lib_try_clone::TryClone;

use crate::immutable::{ImmutableString, MAX_REFERENCES, MaxReferencesExceededError, internal_string::InternalString};

/// Represents possible errors when upgrading a [`WeakImmutableString`] to a [`ImmutableString`].
#[reprc]
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[must_use]
pub enum WeakUpgradeError {
    /// The maximum number of references is exceeded.
    MaxReferencesExceeded = 0,

    /// There are no strong references alive.
    NoStrongReferencesAlive = 1,
}

/// A weak reference to the underlying [`ImmutableString`].
///
/// This object cannot inspect the underlying string. But it does track
/// weak references, and each weak reference can build a strong reference,
/// assuming any other strong reference is alive.
///
/// # Examples
///
/// ```rust
/// # cfg_select! {
/// #    feature="std" => {
/// use osom_lib_strings::immutable::{ImmutableStringError, WeakUpgradeError};
/// use osom_lib_strings::immutable::std::StdImmutableString;
///
/// fn main() -> Result<(), ImmutableStringError> {
///     // The first instance counts as both strong and weak reference.
///     let strong1 = StdImmutableString::from_str_slice("foo")?;
///     assert_eq!(strong1.strong_count(), 1);
///     assert_eq!(strong1.weak_count(), 1);
///     assert_eq!(strong1, "foo");
///
///     let weak = strong1.downgrade().unwrap();  // Cheap operation
///     assert_eq!(strong1.strong_count(), 1);
///     assert_eq!(strong1.weak_count(), 2);
///     assert_eq!(weak.strong_count(), 1);
///     assert_eq!(weak.weak_count(), 2);
///
///     let strong2 = weak.upgrade().unwrap();  // Cheap operation
///     assert_eq!(strong1.strong_count(), 2);
///     assert_eq!(strong1.weak_count(), 2);
///     assert_eq!(weak.strong_count(), 2);
///     assert_eq!(weak.weak_count(), 2);
///     assert_eq!(strong2, strong1);
///
///     drop(strong2);
///     assert_eq!(strong1.strong_count(), 1);
///     assert_eq!(strong1.weak_count(), 2);
///     assert_eq!(weak.strong_count(), 1);
///     assert_eq!(weak.weak_count(), 2);
///
///     drop(strong1);
///     // Both counters are decreased, since there are no more strong references.
///     assert_eq!(weak.strong_count(), 0);
///     assert_eq!(weak.weak_count(), 1);
///
///     // No more strong reference, .upgrade() won't work.
///     assert!(matches!(weak.upgrade(), Err(WeakUpgradeError::NoStrongReferencesAlive)));
///
///     drop(weak);  // The actual deallocation happens here.
///     Ok(())
/// }
/// #   },
/// #   _ => { }
/// # }
/// ```
#[reprc]
#[repr(transparent)]
#[derive(Debug)]
#[must_use]
pub struct WeakImmutableString<TAllocator: Allocator> {
    internal: InternalString<TAllocator>,
}

impl<TAllocator: Allocator> WeakImmutableString<TAllocator> {
    #[inline(always)]
    pub(crate) fn from_internal(internal: InternalString<TAllocator>) -> Self {
        Self { internal }
    }

    #[inline(always)]
    #[must_use]
    pub fn strong_count(&self) -> u32 {
        self.internal.strong().load(Ordering::Relaxed)
    }

    #[inline(always)]
    #[must_use]
    pub fn weak_count(&self) -> u32 {
        self.internal.weak().load(Ordering::Relaxed)
    }

    /// Upgrades current weak reference to the strong [`ImmutableString`].
    ///
    ///
    /// # Errors
    ///
    /// For details see [`WeakUpgradeError`].
    pub fn upgrade(&self) -> Result<ImmutableString<TAllocator>, WeakUpgradeError> {
        let strong = self.internal.strong();
        let mut current = strong.load(Ordering::Relaxed);
        loop {
            if current == 0 {
                return Err(WeakUpgradeError::NoStrongReferencesAlive);
            }

            if current >= MAX_REFERENCES {
                return Err(WeakUpgradeError::MaxReferencesExceeded);
            }

            match strong.compare_exchange_weak(current, current + 1, Ordering::Acquire, Ordering::Relaxed) {
                Ok(_) => return Ok(ImmutableString::from_internal(self.internal.clone())),
                Err(new) => current = new,
            }
        }
    }

    /// Abandons current weak reference.
    ///
    /// If the internal weak counter is positive it returns false.
    ///
    /// Otherwise it deallocates the underlying memory and returns true.
    /// In particular only single (the last) [`WeakImmutableString`] returns true
    /// by calling this.
    #[inline(always)]
    #[must_use]
    pub fn abandon(mut self) -> bool {
        let result = self.internal_abandon();
        core::mem::forget(self);
        result
    }

    fn internal_abandon(&mut self) -> bool {
        let internal = unsafe { core::ptr::read(&raw const self.internal) };
        let prev = internal.weak().fetch_sub(1, Ordering::Release);
        if prev > 1 {
            return false;
        }

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

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

impl<TAllocator: Allocator> TryClone for WeakImmutableString<TAllocator> {
    type Error = MaxReferencesExceededError;

    fn try_clone(&self) -> Result<Self, Self::Error> {
        let internal_clone = self.internal.clone();
        let prev_value = internal_clone.weak().fetch_add(1, Ordering::Relaxed);
        if prev_value >= MAX_REFERENCES {
            internal_clone.weak().fetch_sub(1, Ordering::Relaxed);
            return Err(MaxReferencesExceededError);
        }

        Ok(Self {
            internal: internal_clone,
        })
    }
}

impl<TAllocator: Allocator> Clone for WeakImmutableString<TAllocator> {
    fn clone(&self) -> Self {
        self.try_clone()
            .expect("WeakImmutableString weak reference count is too high.")
    }
}