cycle_ptr 0.1.1

Smart pointers, with cycles
Documentation
//! Declare all errors that this code can generate.
use std::backtrace::Backtrace;
use std::fmt;
use std::sync::Arc;

#[cfg(feature = "multi_thread")]
use std::io;

/// Error codes for the cyclic pointer library.
///
/// We hide this, because we may hide codes depending on features being enabled.
#[derive(Clone, Debug)]
pub(crate) enum ErrorEnum {
    /// Error that indicates a callback can't be installed, because one exists already.
    CallbackAlreadyInstalled,
    /// [Weak pointer][crate::Weak] upgrade failed because the object has not been constructed yet.
    #[cfg(feature = "weak_pointer")]
    NotYetInitialized(Option<Arc<Backtrace>>),
    /// [Weak pointer][crate::Weak] upgrade failed because the object has been expired.
    #[cfg(feature = "weak_pointer")]
    Expired(Option<Arc<Backtrace>>),
    /// [Weak pointer][crate::Weak] upgrade failed because the weak pointer was never initialized with a value.
    #[cfg(feature = "weak_pointer")]
    UnvaluedWeak,
    /// An internal lock is poisoned.
    #[cfg(feature = "multi_thread")]
    InternalLockingError,
    /// Dereference of a member-pointer failed due to the origin being dropped.
    OriginExpired(Option<Arc<Backtrace>>),
    /// Indicates we could not start garbage collector thread.
    #[cfg(feature = "multi_thread")]
    ThreadSpawnFailed(Arc<io::Error>),
}

impl Eq for ErrorEnum {}

impl PartialEq for ErrorEnum {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (ErrorEnum::CallbackAlreadyInstalled, ErrorEnum::CallbackAlreadyInstalled) => true,
            #[cfg(feature = "weak_pointer")]
            (ErrorEnum::NotYetInitialized(_), ErrorEnum::NotYetInitialized(_)) => true,
            #[cfg(feature = "weak_pointer")]
            (ErrorEnum::Expired(_), ErrorEnum::Expired(_)) => true,
            #[cfg(feature = "weak_pointer")]
            (ErrorEnum::UnvaluedWeak, ErrorEnum::UnvaluedWeak) => true,
            #[cfg(feature = "multi_thread")]
            (ErrorEnum::InternalLockingError, ErrorEnum::InternalLockingError) => true,
            (ErrorEnum::OriginExpired(_), ErrorEnum::OriginExpired(_)) => true,
            #[cfg(feature = "multi_thread")]
            (ErrorEnum::ThreadSpawnFailed(_), ErrorEnum::ThreadSpawnFailed(_)) => true,
            _ => false,
        }
    }
}

/// Errors for the cyclic pointer library.
///
/// The [Debug][fmt::Debug] and [Display][fmt::Display] trait implementations
/// give a helpful description on what the reason for the error is.
#[derive(Clone, PartialEq, Eq)]
pub struct Error {
    /// The actual code.
    /// This is used to populate the error message.
    code: ErrorEnum,
}

impl Error {
    /// Create a new error with the given error code.
    #[inline]
    pub(crate) const fn new(code: ErrorEnum) -> Error {
        Error { code }
    }

    /// If this object is due to attempting to interact with an object,
    /// retrieve the backtrace of when this object was created.
    #[inline]
    pub fn object_creation_backtrace(&self) -> Option<&Backtrace> {
        match &self.code {
            ErrorEnum::CallbackAlreadyInstalled => None,
            #[cfg(feature = "weak_pointer")]
            ErrorEnum::NotYetInitialized(opt_backtrace) => {
                opt_backtrace.as_ref().map(|backtrace| &**backtrace)
            }
            #[cfg(feature = "weak_pointer")]
            ErrorEnum::Expired(opt_backtrace) => {
                opt_backtrace.as_ref().map(|backtrace| &**backtrace)
            }
            #[cfg(feature = "weak_pointer")]
            ErrorEnum::UnvaluedWeak => None,
            #[cfg(feature = "multi_thread")]
            ErrorEnum::InternalLockingError => None,
            ErrorEnum::OriginExpired(opt_backtrace) => {
                opt_backtrace.as_ref().map(|backtrace| &**backtrace)
            }
            #[cfg(feature = "multi_thread")]
            ErrorEnum::ThreadSpawnFailed(_) => None,
        }
    }
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
    #[allow(
        clippy::missing_inline_in_public_items,
        reason = "Inlining this function won't generate much benefit."
    )]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        enum MaybeBacktrace<'a> {
            /// There is never a backtrace associated with this error.
            Never,
            /// There could be a backtrace associated with this error, had backtraces not been disabled/unsupported.
            None,
            /// There is a backtrace.
            Some(&'a Backtrace),
        }

        impl<'a> MaybeBacktrace<'a> {
            fn new(opt_backtrace: &'a Option<Arc<Backtrace>>) -> Self {
                opt_backtrace
                    .as_ref()
                    .map_or(MaybeBacktrace::None, |backtrace| {
                        MaybeBacktrace::Some(backtrace)
                    })
            }
        }

        let (msg, opt_backtrace): (&'static str, MaybeBacktrace) = match &self.code {
            ErrorEnum::CallbackAlreadyInstalled => {
                ("callback already installed", MaybeBacktrace::Never)
            }
            #[cfg(feature = "weak_pointer")]
            ErrorEnum::NotYetInitialized(opt_backtrace) => (
                "weak pointer points at not-yet-initialized object",
                MaybeBacktrace::new(opt_backtrace),
            ),
            #[cfg(feature = "weak_pointer")]
            ErrorEnum::Expired(opt_backtrace) => {
                ("object has expired", MaybeBacktrace::new(opt_backtrace))
            }
            #[cfg(feature = "weak_pointer")]
            ErrorEnum::UnvaluedWeak => ("weak pointer never had a value", MaybeBacktrace::Never),
            #[cfg(feature = "multi_thread")]
            ErrorEnum::InternalLockingError => ("internal lock is poisoned", MaybeBacktrace::Never),
            ErrorEnum::OriginExpired(opt_backtrace) => (
                "unable to dereference/clone member-pointer, because its origin has expired",
                MaybeBacktrace::new(opt_backtrace),
            ),
            #[cfg(feature = "multi_thread")]
            ErrorEnum::ThreadSpawnFailed(io_err) => {
                return write!(f, "unable to start GC thread: {}", io_err);
            }
        };

        match opt_backtrace {
            MaybeBacktrace::Never => write!(f, "{}", msg),
            MaybeBacktrace::None => write!(
                f,
                "{}\nuse RUST_BACKTRACE=1 to get a backtrace of when the pointer was constructed",
                msg,
            ),
            MaybeBacktrace::Some(backtrace) => {
                write!(f, "{}\nobject constructed at:\n{}", msg, backtrace)
            }
        }
    }
}

impl fmt::Debug for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        fmt::Debug::fmt(&self.code, f)
    }
}

#[cfg(test)]
mod tests {
    use super::{Error, ErrorEnum};

    fn is_send<T: Send>(_: &T) -> bool {
        true
    }

    fn is_sync<T: Sync>(_: &T) -> bool {
        true
    }

    #[test]
    fn error_is_send_and_sync() {
        let e = Error::new(ErrorEnum::CallbackAlreadyInstalled);

        assert!(is_send(&e));
        assert!(is_sync(&e));
    }
}