cycle_ptr 0.1.0

Smart pointers, with cycles
//! Module for the sendable weak pointer type.
use super::sync_ptr::GcMtPtr;
use crate::GcMemberPtr;
use crate::GcPtr;
use crate::errors::{Error, ErrorEnum};
use crate::object::MTObjectPtr;
use crate::pointer::SingleOrMultiThreadPtr;
use crate::prelude::GcPtrEq;
use crate::sync::GcMtMemberPtr;
use std::fmt;
use std::pin::Pin;
use std::ptr;

/// A weak reference to an object.
#[cfg_attr(docsrs, doc(cfg(feature = "multi_thread", feature = "weak_pointer")))]
pub struct Weak<T>
where
    T: 'static + Send + Sync,
{
    /// Actual pointer to [MTObject][crate::object::MTObject].
    pub(super) ptr: Option<Pin<MTObjectPtr<T>>>,
}

impl<T> Weak<T>
where
    T: 'static + Send + Sync,
{
    /// Create an empty [Weak].
    #[inline]
    pub const fn new() -> Self {
        Weak { ptr: None }
    }

    /// Create a new [Weak].
    #[inline]
    pub(super) const fn new_ptr(ptr: Pin<MTObjectPtr<T>>) -> Self {
        Weak { ptr: Some(ptr) }
    }

    /// Create a new [Weak] from an [MTObjectPtr].
    #[inline]
    pub(crate) const fn new_from_raw(ptr: Pin<MTObjectPtr<T>>) -> Self {
        Self::new_ptr(ptr)
    }

    /// Try to upgrade the weak pointer to a regular [GcMtPtr].
    ///
    /// # Errors
    ///
    /// Will return [Error] if the weak pointer is no longer valid.
    #[inline]
    pub fn upgrade(&self) -> Result<GcMtPtr<T>, Error> {
        self.ptr
            .as_ref()
            .ok_or(Error::new(ErrorEnum::UnvaluedWeak))
            .and_then(GcMtPtr::new_from_weak)
    }
}

impl<T> GcPtrEq<GcPtr<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &GcPtr<T>) -> bool {
        GcPtr::ptr_eq(other, this)
    }
}

#[cfg(feature = "multi_thread")]
impl<T> GcPtrEq<GcMtPtr<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &GcMtPtr<T>) -> bool {
        GcMtPtr::ptr_eq(other, this)
    }
}

impl<T> GcPtrEq<GcMemberPtr<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &GcMemberPtr<T>) -> bool {
        GcMemberPtr::ptr_eq(other, this)
    }
}

#[cfg(feature = "multi_thread")]
impl<T> GcPtrEq<GcMtMemberPtr<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &GcMtMemberPtr<T>) -> bool {
        GcMtMemberPtr::ptr_eq(other, this)
    }
}

impl<T> GcPtrEq<crate::Weak<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &crate::Weak<T>) -> bool {
        match (&this.ptr, &other.ptr) {
            (Some(this_ptr), Some(SingleOrMultiThreadPtr::MultiThread(other_ptr))) => {
                ptr::eq(&**this_ptr, &**other_ptr)
            }
            _ => false,
        }
    }
}

#[cfg(feature = "multi_thread")]
impl<T> GcPtrEq<Weak<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &Weak<T>) -> bool {
        match (&this.ptr, &other.ptr) {
            (Some(this_ptr), Some(other_ptr)) => ptr::eq(&**this_ptr, &**other_ptr),
            _ => false,
        }
    }
}

impl<T> Default for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn default() -> Self {
        Weak::new()
    }
}

impl<T> Clone for Weak<T>
where
    T: 'static + Send + Sync,
{
    #[inline]
    fn clone(&self) -> Self {
        Weak {
            ptr: self.ptr.clone(),
        }
    }
}

impl<T> fmt::Debug for Weak<T>
where
    T: 'static + Send + Sync + fmt::Debug,
{
    #[allow(
        clippy::missing_inline_in_public_items,
        reason = "This will make a call to some complicated debug functions. Might as well make it non-inline."
    )]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        match &self.ptr {
            None => f.write_str("Weak(None)"),
            Some(ptr) => ptr.weak_debug_fmt("Weak", f),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Weak;
    use crate::errors::{Error, ErrorEnum};
    use crate::pointer::sync::GcMtPtr;
    use crate::prelude::GcPtrEq;

    #[test]
    fn weak_can_promote() {
        let ptr = GcMtPtr::new(|_| "bla".to_owned());
        let weak_ptr: Weak<String> = GcMtPtr::downgrade(&ptr);

        let q = Weak::upgrade(&weak_ptr);
        assert!(q.is_ok());
        assert!(GcMtPtr::ptr_eq(&ptr, &q.unwrap()));
    }

    #[test]
    #[cfg_attr(
        feature = "single_generation_mt",
        ignore = "Single generation GC runs at unspecified time"
    )]
    fn expired_weak_cant_promote() {
        let ptr = GcMtPtr::new(|_| "bla".to_owned());
        let weak_ptr: Weak<String> = GcMtPtr::downgrade(&ptr);
        drop(ptr);

        let q = Weak::upgrade(&weak_ptr);
        assert!(q.is_err());
        assert_eq!(q.err().unwrap(), Error::new(ErrorEnum::Expired(None)));
    }

    #[test]
    fn empty_weak_cant_promote() {
        let weak_ptr: Weak<String> = Weak::default();

        let q = Weak::upgrade(&weak_ptr);
        assert!(q.is_err());
        assert_eq!(q.err().unwrap(), Error::new(ErrorEnum::UnvaluedWeak));
    }
}