junkdrawer 0.1.0

A crate for all kinds of utilities.
Documentation
//! This module contains a _very unsafe_ synchronization primitive that can be used to micromanage your code or really mess things up.
//!
//! This is somewhat similar to [`std::rc::Rc`]+[`std::rc::Weak`] or [`std::sync::Arc`]+[`std::sync::Weak`] but instead of the `Weak`-Counterpart knowing whether the data is still alive, well, they just don't.
//! In addition, this provides the functionality of a [`std::cell::RefCell`] or [`std::sync::Mutex`]/[`std::sync::RwLock`] but again,
//! instead of checking for concurrent mutable access, well, they just don't.
//!
//! In short, if you do this right you get a zero-cost `Rc<RefCell<T>>` or `Arc<Mutex<T>>` that is also `Copy`.
//! If you misuse them, well you get aliased mutable access to deallocated memory.
//! Have fun!
//!
//! (Have I mentioned these types are really unsafe?)
//!
//! In case you don't trust yourself with these, there is the feature `shared-owned-debug`.
//! With this feature the structure remains `Copy` but it leaks a single byte to check if data is still valid.
//! On first read/write access this byte is checked and if the memory is deallocated it will panic.
//! However, you can still deallocate after reading/writing started and that won't be caught.
//! So in that case, please check periodically, if the data you are r/w-ing is still alive (and valid).
//!

use std::cell::UnsafeCell;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::ptr::NonNull;

/// This struct is like a box, but it allows you to create clones (without reference counting or nasty borrow checks)
///
/// # Memory Leaks:
/// When the feature `shared-owned-debug` is enabled, each [`Owned`] will leak one byte.
/// This byte is an atomic bool that is used to keep track of the state (alive/dead of the allocation).
/// However, this byte is only used when reading the reference from the [`Shared`].
/// If the [`Shared`] reads the data, then the [`Owned`] is dropped, no panic will be triggered.
///
/// This feature does not affect any implemented trait, expect that [`Owned`] implements [`Drop`] and as a result [`Owned::into_inner`] is no longer available.
///
/// # Safety
/// You are responsible for:
/// - Dropping any derived [`Shared`] before this one drops (or at least to never use them again)
/// - Making sure that mutable access never happens concurrently.
#[cfg_attr(not(feature = "shared-owned-debug"), repr(transparent))]
pub struct Owned<T> {
    inner: Option<NonNull<UnsafeCell<T>>>,
    #[cfg(feature = "shared-owned-debug")]
    safety: NonNull<AtomicBool>,
}

impl<T> Owned<T> {
    /// Creates a new [`Owned`] from a value.
    pub fn new(inner: T) -> Self {
        Self {
            inner: NonNull::new(Box::into_raw(Box::new(UnsafeCell::new(inner)))),
            #[cfg(feature = "shared-owned-debug")]
            safety: {
                let boxed = Box::new(AtomicBool::new(true));
                let safety = NonNull::from_ref(&*boxed);
                let _ = Box::leak(boxed);
                safety
            },
        }
    }

    /// Creates a share for this allocation.
    #[must_use]
    #[allow(clippy::missing_panics_doc)]
    pub const fn share(&self) -> Shared<T> {
        Shared {
            ptr: self
                .inner
                .expect("Inner will only turn into a none on drop."),
            #[cfg(feature = "shared-owned-debug")]
            safety: self.safety,
        }
    }

    /// # Safety
    /// You have to make sure that no mutable reference to the data exists
    #[must_use]
    #[allow(clippy::missing_panics_doc)]
    pub const unsafe fn get(&self) -> &T {
        unsafe {
            &*self
                .inner
                .as_ref()
                .expect("Inner will only turn into a none on drop.")
                .as_ref()
                .get()
        }
    }

    /// # Safety
    /// You have to make sure that no other references to the data exist
    #[must_use]
    #[allow(clippy::missing_panics_doc)]
    pub const unsafe fn get_mut(&mut self) -> &mut T {
        unsafe {
            self.inner
                .as_mut()
                .expect("Inner will only turn into a none on drop.")
                .as_mut()
                .get_mut()
        }
    }

    #[must_use]
    pub fn derived(&self, shared: &Shared<T>) -> bool {
        self.share().same_origin(shared)
    }

    /// Drops this [`Owned`] and returns the underlying data.
    ///
    /// From here on any derived [`Shared`] is dangling.
    #[must_use]
    #[allow(clippy::missing_panics_doc)]
    pub fn into_inner(mut self) -> T {
        unsafe {
            Box::from_raw(
                self.inner
                    .take()
                    .expect("Inner will only turn into a none on drop.")
                    .as_ptr(),
            )
        }
            .into_inner()
    }
}

impl<T> Drop for Owned<T> {
    fn drop(&mut self) {
        #[cfg(feature = "shared-owned-debug")]
        unsafe { self.safety.as_ref() }.store(false, std::sync::atomic::Ordering::SeqCst);
        if let Some(ptr) = self.inner.take() {
            // Create and drop the box
            let _ = unsafe { Box::from_raw(ptr.as_ptr()) };
        }
    }
}

/// This is a shared reference to an [`Owned`].
///
/// Shared always implements [`Copy`], [`Clone`], [`PartialEq`], [`PartialOrd`] and [`Ord`].
/// Since [`Shared`] is just a wrapper around [`NonNull`], a pointer, `Clone` and `Copy` should be obvious.
/// `Hash`, `PartialEq`, `Eq`, `PartialOrd` and `Ord` compare memory addresses, this might be unintuitive but makes working with them more convenient (in some way).
///
/// The behavior of this is hard to predict, but this is meant to e.g. keep a list of known-dangling [`Shared`] in either a sorted list or a set.
/// You can then check if the list/set contains your thingy and if yes, it is not safe to use.
///
/// Beware that memory could be reused at a later time, so be sure to get all dangling pointers out of your system before that happens.
///
/// This struct is meant to make mutable-shared access easier in cases where you know things will not go wrong, by order of operation.
///
/// # Examples
///
/// ```rust
/// use bip_lib::prelude::{Owned, Shared};
///
/// enum Jobs {
///     Start(String),
///     ProcessA(Shared<String>),
///     ProcessB(Shared<String>),
///     Drop(Owned<String>)
/// }
///
/// let mut queue = vec![Jobs::Start("Hello World".to_string())];
///
/// while let Some(job) = queue.pop() {
///     match job {
///         Jobs::Start(string) => {
///             let owned = Owned::new(string);
///             let shared = owned.share();
///             queue.push(Jobs::Drop(owned));
///             queue.push(Jobs::ProcessB(shared));
///             queue.push(Jobs::ProcessA(shared));
///
///         },
///         Jobs::ProcessA(shared) => {
///             let s = unsafe {shared.get()};
///             if s.is_empty() {
///                 println!("No substring to process");
///             } else {
///                 println!("Processing first half");
///                 queue.push(Jobs::Start(s[..(s.len()/2)].to_string()))
///             }
///         },
///         Jobs::ProcessB(shared) => {
///             println!("Sub-jobs of {} were processed", unsafe {shared.get()});
///         },
///         Jobs::Drop(owned) => {
///             println!("Dropping {}", unsafe { owned.get() })
///         }
///     }
/// }
///
/// ```
///
/// In this structure (a queue), the job that owns the data will be popped last, as such the others can reference the same data.
/// However, working with lifetimes does not work in this case and reference counting + `RefCell` for the occasional mutable access would introduce a lot of overhead to check borrow rules that will never be violated.
///
///
#[cfg_attr(not(feature = "shared-owned-debug"), repr(transparent))]
pub struct Shared<T> {
    ptr: NonNull<UnsafeCell<T>>,
    #[cfg(feature = "shared-owned-debug")]
    safety: NonNull<AtomicBool>,
}

impl<T> Clone for Shared<T> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<T> Copy for Shared<T> {}

impl<T> Hash for Shared<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.ptr.addr().hash(state);
    }
}

impl<T> PartialEq for Shared<T> {
    fn eq(&self, other: &Self) -> bool {
        self.ptr.as_ptr().addr() == other.ptr.as_ptr().addr()
    }
}

impl<T> Eq for Shared<T> {}

impl<T> PartialOrd for Shared<T> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl<T> Ord for Shared<T> {
    fn cmp(&self, other: &Self) -> Ordering {
        self.ptr.addr().cmp(&other.ptr.addr())
    }
}

impl<T> Shared<T> {
    /// # Safety
    /// You are responsible for:
    /// - Making sure that the owning [`Shared`] this was derived from still exists.
    /// - Making sure only immutable references to the data exist
    #[must_use]
    #[track_caller]
    #[const_fn::const_fn(cfg(not(feature = "shared-owned-debug")))]
    pub const unsafe fn get(&self) -> &T {
        #[cfg(feature = "shared-owned-debug")]
        if !self.is_alive() {
            panic!("Tried to read deallocated shared value");
        }

        unsafe { &*self.ptr.as_ref().get() }
    }

    /// # Safety
    /// You are responsible for:
    /// - Making sure that the owning [`Shared`] this was derived from still exists.
    /// - Making sure that no one else currently has a pointer.
    #[must_use]
    #[track_caller]
    #[const_fn::const_fn(cfg(not(feature = "shared-owned-debug")))]
    pub const unsafe fn get_mut(&mut self) -> &mut T {
        #[cfg(feature = "shared-owned-debug")]
        if !self.is_alive() {
            panic!("Tried to read deallocated shared value");
        }

        unsafe { self.ptr.as_mut() }.get_mut()
    }

    /// Checks if these two share the same origin.
    ///
    /// Can be used to find out if a shared is dangling.
    /// E.g. maintain a list (sorted by mem-address) that you add a share for every deallocated [`Owned`] to.
    /// You can then check if your [`Shared`] is contained in that list.
    #[must_use]
    pub fn same_origin(&self, other: &Self) -> bool {
        self.ptr.as_ptr() == other.ptr.as_ptr()
    }

    #[must_use]
    #[cfg(feature = "shared-owned-debug")]
    pub fn is_alive(&self) -> bool {
        unsafe { self.safety.as_ref() }.load(std::sync::atomic::Ordering::SeqCst)
    }
}

#[cfg(test)]
mod owned_shared_tests {
    use super::Owned;
    use droptest::{DropRegistry, assert_drop, assert_no_drop};

    #[test]
    pub fn elements_are_dropped() {
        let registry = DropRegistry::default();
        let guard = registry.new_guard_for(420_usize);
        let id = guard.id();

        let owned = Owned::new(guard);

        assert_no_drop!(
            registry,
            id,
            "Creating an owned version from a value somehow dropped it?"
        );

        let shared = owned.share();

        assert_eq!(
            *unsafe { shared.get() }.value(),
            420_usize,
            "The wrong value was in the shared value"
        );

        assert_no_drop!(
            registry,
            id,
            "The owned value got dropped while creating a shared value."
        );

        drop(owned);

        assert_drop!(
            registry,
            id,
            "The value still exists, even though it should have been dropped."
        );
    }
}