cycle_ptr 0.1.0

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

#[cfg(feature = "multi_thread")]
use super::SingleOrMultiThreadPtr;
#[cfg(feature = "multi_thread")]
use super::sync_ptr::GcMtPtr;
#[cfg(feature = "multi_thread")]
use crate::sync::GcMtMemberPtr;

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

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

    /// Create a new [Weak].
    #[cfg(not(feature = "multi_thread"))]
    #[inline]
    pub(super) const fn new_ptr(ptr: Pin<ObjectPtr<T>>) -> Self {
        Weak { ptr: Some(ptr) }
    }

    /// Create a new [Weak].
    #[cfg(feature = "multi_thread")]
    #[inline]
    pub(super) const fn new_ptr(ptr: SingleOrMultiThreadPtr<T>) -> Self {
        Weak { ptr: Some(ptr) }
    }

    /// Create a new [Weak] from an [ObjectPtr].
    #[inline]
    pub(crate) const fn new_from_raw(ptr: Pin<ObjectPtr<T>>) -> Self {
        #[cfg(not(feature = "multi_thread"))]
        return Self::new_ptr(ptr);
        #[cfg(feature = "multi_thread")]
        return Self::new_ptr(SingleOrMultiThreadPtr::SingleThread(ptr));
    }

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

impl<T> GcPtrEq<GcPtr<T>> for Weak<T>
where
    T: 'static,
{
    #[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,
{
    #[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<Weak<T>> for Weak<T>
where
    T: 'static,
{
    #[inline]
    fn ptr_eq(this: &Self, other: &Weak<T>) -> bool {
        #[cfg(not(feature = "multi_thread"))]
        return match (&this.ptr, &other.ptr) {
            (Some(this_ptr), Some(other_ptr)) => ptr::eq(&**this_ptr, &**other_ptr),
            _ => false,
        };
        #[cfg(feature = "multi_thread")]
        return match (&this.ptr, &other.ptr) {
            (
                Some(SingleOrMultiThreadPtr::SingleThread(this_ptr)),
                Some(SingleOrMultiThreadPtr::SingleThread(other_ptr)),
            ) => ptr::eq(&**this_ptr, &**other_ptr),
            (
                Some(SingleOrMultiThreadPtr::MultiThread(this_ptr)),
                Some(SingleOrMultiThreadPtr::MultiThread(other_ptr)),
            ) => ptr::eq(&**this_ptr, &**other_ptr),
            _ => false,
        };
    }
}

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

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

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

#[cfg(feature = "multi_thread")]
impl<T> From<super::sync_weak::Weak<T>> for Weak<T>
where
    T: 'static + Send + Sync,
{
    /// Convert from [thread-safe Weak][super::sync_weak::Weak] to [Weak].
    #[inline]
    fn from(v: super::sync_weak::Weak<T>) -> Weak<T> {
        Weak {
            ptr: v.ptr.map(|ptr| SingleOrMultiThreadPtr::MultiThread(ptr)),
        }
    }
}

impl<T> fmt::Debug for Weak<T>
where
    T: 'static + fmt::Debug,
{
    #[cfg(not(feature = "multi_thread"))]
    #[inline(never)]
    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(feature = "multi_thread")]
    #[inline(never)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        match &self.ptr {
            None => f.write_str("Weak(None)"),
            Some(SingleOrMultiThreadPtr::SingleThread(ptr)) => ptr.weak_debug_fmt("Weak", f),
            Some(SingleOrMultiThreadPtr::MultiThread(ptr)) => ptr.weak_debug_fmt("Weak", f),
        }
    }
}

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

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

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

    #[test]
    #[cfg_attr(
        feature = "single_generation",
        ignore = "Single generation GC runs at unspecified time"
    )]
    fn expired_weak_cant_promote() {
        let ptr = GcPtr::new(|_| "bla".to_owned());
        let weak_ptr: Weak<String> = GcPtr::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));
    }
}