try-drop 0.2.0

Batteries included error handling mechanisms for drops which can fail
Documentation
pub mod scope_guard;
pub(crate) mod imports {
    pub use crate::handlers::UninitializedError;
    pub use crate::{DynFallibleTryDropStrategy, ThreadLocalFallibleTryDropStrategy};
    pub use std::boxed::Box;
}

use crate::handlers::common::thread_local::scope_guard::ScopeGuard;
use crate::handlers::common::Handler;
use crate::handlers::UninitializedError;
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::thread::LocalKey;

macro_rules! thread_local_methods {
    (
        ThreadLocal = $thread_local:ident;
        ScopeGuard = $scope_guard:ident;
        GenericStrategy = $generic_strategy:ident;
        DynStrategy = $dyn_strategy:ident;
        feature = $feature:literal;

        $(#[$($install_meta:meta)*])*
        install;

        $(#[$($install_dyn_meta:meta)*])*
        install_dyn;

        $(#[$($read_meta:meta)*])*
        read;

        $(#[$($try_read_meta:meta)*])*
        try_read;

        $(#[$($read_or_default_meta:meta)*])*
        read_or_default;

        $(#[$($write_meta:meta)*])*
        write;

        $(#[$($try_write_meta:meta)*])*
        try_write;

        $(#[$($write_or_default_meta:meta)*])*
        write_or_default;

        $(#[$($uninstall_meta:meta)*])*
        uninstall;

        $(#[$($take_meta:meta)*])*
        take;

        $(#[$($replace_meta:meta)*])*
        replace;

        $(#[$($replace_dyn_meta:meta)*])*
        replace_dyn;

        $(#[$($scope_meta:meta)*])*
        scope;

        $(#[$($scope_dyn_meta:meta)*])*
        scope_dyn;
    ) => {
        #[allow(unused_imports)]
        use $crate::handlers::common::thread_local::imports::*;

        $(#[$($install_meta)*])*
        pub fn install(strategy: impl $generic_strategy) {
            $thread_local::install(strategy)
        }

        $(#[$($install_dyn_meta)*])*
        pub fn install_dyn(strategy: $dyn_strategy) {
            $thread_local::install_dyn(strategy)
        }

        $(#[$($read_meta)*])*
        pub fn read<T>(f: impl FnOnce(&$dyn_strategy) -> T) -> T {
            $thread_local::read(f)
        }

        $(#[$($try_read_meta)*])*
        pub fn try_read<T>(f: impl FnOnce(&$dyn_strategy) -> T) -> Result<T, UninitializedError> {
            $thread_local::try_read(f)
        }

        $(#[$($read_or_default_meta)*])*
        #[cfg(feature = $feature)]
        pub fn read_or_default<T>(f: impl FnOnce(&$dyn_strategy) -> T) -> T {
            $thread_local::read_or_default(f)
        }

        $(#[$($write_meta)*])*
        pub fn write<T>(f: impl FnOnce(&mut $dyn_strategy) -> T) -> T {
            $thread_local::write(f)
        }

        $(#[$($try_write_meta)*])*
        pub fn try_write<T>(f: impl FnOnce(&mut $dyn_strategy) -> T) -> Result<T, UninitializedError> {
            $thread_local::try_write(f)
        }

        $(#[$($write_or_default_meta)*])*
        #[cfg(feature = $feature)]
        pub fn write_or_default<T>(f: impl FnOnce(&mut $dyn_strategy) -> T) -> T {
            $thread_local::write_or_default(f)
        }

        $(#[$($uninstall_meta)*])*
        pub fn uninstall() {
            $thread_local::uninstall()
        }

        $(#[$($take_meta)*])*
        pub fn take() -> Option<$dyn_strategy> {
            $thread_local::take()
        }

        $(#[$($replace_meta)*])*
        pub fn replace(strategy: impl $generic_strategy) -> Option<$dyn_strategy> {
            $thread_local::replace(strategy)
        }

        $(#[$($replace_dyn_meta)*])*
        pub fn replace_dyn(strategy: $dyn_strategy) -> Option<$dyn_strategy> {
            $thread_local::replace_dyn(strategy)
        }

        $(#[$($scope_meta)*])*
        pub fn scope(strategy: impl $generic_strategy) -> $scope_guard {
            $thread_local::scope(strategy)
        }

        $(#[$($scope_dyn_meta)*])*
        pub fn scope_dyn(strategy: $dyn_strategy) -> $scope_guard {
            $thread_local::scope_dyn(strategy)
        }
    };
}

pub trait ThreadLocalDefinition: Handler {
    const UNINITIALIZED_ERROR: &'static str;
    const DYN: &'static str;
    type ThreadLocal: 'static;

    fn thread_local() -> &'static LocalKey<RefCell<Option<Self::ThreadLocal>>>;
    fn locked() -> &'static LocalKey<Cell<bool>>;
}

pub trait DefaultThreadLocalDefinition: ThreadLocalDefinition {
    fn default() -> Self::ThreadLocal;
}

pub struct ThreadLocal<T: ThreadLocalDefinition>(PhantomData<T>);

impl<T: ThreadLocalDefinition> ThreadLocal<T> {
    pub fn read<R>(f: impl FnOnce(&T::ThreadLocal) -> R) -> R {
        Self::try_read(f).expect(T::UNINITIALIZED_ERROR)
    }

    pub fn try_read<R>(f: impl FnOnce(&T::ThreadLocal) -> R) -> Result<R, UninitializedError> {
        T::thread_local().with(|cell| {
            cell.borrow_mut()
                .as_ref()
                .map(f)
                .ok_or(UninitializedError(()))
        })
    }

    pub fn write<R>(f: impl FnOnce(&mut T::ThreadLocal) -> R) -> R {
        Self::try_write(f).expect(T::UNINITIALIZED_ERROR)
    }

    pub fn try_write<R>(f: impl FnOnce(&mut T::ThreadLocal) -> R) -> Result<R, UninitializedError> {
        T::thread_local().with(|cell| {
            cell.borrow_mut()
                .as_mut()
                .map(f)
                .ok_or(UninitializedError(()))
        })
    }

    pub fn install(strategy: impl Into<T::ThreadLocal>) {
        Self::install_dyn(strategy.into())
    }

    pub fn install_dyn(strategy: T::ThreadLocal) {
        Self::replace_dyn(strategy);
    }

    pub fn uninstall() {
        Self::take();
    }

    pub fn take() -> Option<T::ThreadLocal> {
        T::thread_local().with(|cell| cell.borrow_mut().take())
    }

    pub fn replace(new: impl Into<T::ThreadLocal>) -> Option<T::ThreadLocal> {
        Self::replace_dyn(new.into())
    }

    pub fn replace_dyn(new: T::ThreadLocal) -> Option<T::ThreadLocal> {
        T::thread_local().with(|cell| cell.borrow_mut().replace(new))
    }

    pub fn scope(strategy: impl Into<T::ThreadLocal>) -> ScopeGuard<T> {
        Self::scope_dyn(strategy.into())
    }

    pub fn scope_dyn(strategy: T::ThreadLocal) -> ScopeGuard<T> {
        ScopeGuard::new_dyn(strategy)
    }
}

impl<T: DefaultThreadLocalDefinition> ThreadLocal<T> {
    pub fn read_or_default<R>(f: impl FnOnce(&T::ThreadLocal) -> R) -> R {
        T::thread_local().with(|cell| {
            let mut strategy = cell.borrow_mut();
            let strategy = strategy.get_or_insert_with(T::default);
            let strategy = &*strategy;
            f(strategy)
        })
    }

    pub fn write_or_default<R>(f: impl FnOnce(&mut T::ThreadLocal) -> R) -> R {
        T::thread_local().with(|cell| f(cell.borrow_mut().get_or_insert_with(T::default)))
    }
}