#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
#![feature(once_cell)]
#![feature(trait_alias)]
#![feature(generic_associated_types)]
#![feature(associated_type_defaults)]
#![warn(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![warn(clippy::pedantic)]
#![allow(clippy::needless_question_mark)]
#![allow(unused_unsafe)]
/// Useful trait aliases of [`DmvId`] with common types.
pub mod aliases;
mod details;
/// Definition of the [`Id`] traits.
pub mod id;
/// Definition of the [`Scope`] trait and [`scope!`] macro.
pub mod scope;
/// Data structure that maps a [`Scope`]'s [`TypeId`] to a value.
pub mod scope_map;
/// Convenient alias for the [`Id`]-implementing type of the object
/// returned from [`Dmv::register`].
pub type DmvId<S, T> = details::Id<S, T>;
/// Alias of the [`Id::Handle`] associated type of [`DmvId`].
pub type DmvIdHandle<S, T> = <DmvId<S, T> as Id<S, T>>::Handle;
pub use id::{Comparable, Id, RawRepr};
pub use scope::{GlobalScope, Scope};
use details::Id as privId;
use scope_map::ScopeMap;
use std::{
any::{Any, TypeId},
collections::HashMap,
lazy::SyncLazy,
sync::Mutex,
};
use num_traits::Num;
macro_rules! gen_global_scope_inits {
($map:ident<$($T:ty),+ $(,).*>) => {$(
$map.entry(TypeId::of::<$T>())
.or_insert_with(|| {
let mut scope_map = ScopeMap::<$T>::new();
// Safety:
// TODO
unsafe { scope_map.set(TypeId::of::<GlobalScope>(), 1) };
Box::new(scope_map)
});
)*};
($($t:tt)*) => {
compile_error!(concat!(
"encountered unexpected tokens: ",
stringify!($($t)*),
));
};
}
static MAPS: SyncLazy<Mutex<HashMap<TypeId, Box<dyn Any + Send>>>> = SyncLazy::new(|| {
let mut map: HashMap<_, Box<dyn Any + Send>> = HashMap::new();
gen_global_scope_inits!(map<u8, u16, u32, u64, u128, usize>);
Mutex::new(map)
});
/// The administrator of [`Id`]s.
///
/// TODO: Write section describing internal behavior of [`Dmv`] and the term "ID bucket" which is referenced throughout the docs
///
/// # Panics
///
/// All methods on [`Dmv`] may panic if the internal [`Mutex`] ensuring safe
/// global mutable state for [`Dmv`] becomes [poisoned], which occurs when a
/// thread panics while holding the mutex. If the mutex is poisoned, it is also
/// likely that the underlying data is invalid in some way and should not be
/// trusted, and panicking is the safest way to resolve that situation.
///
/// # Pun
///
/// The struct's name, `Dmv`, is a reference to a common state-level agency in
/// the US, the [Department of Motor Vehicles][DMV wiki], where local citizens
/// may acquire a Drivers License/Photo ID.
///
/// [DMV wiki]: https://en.wikipedia.org/wiki/Department_of_motor_vehicles
/// [poisoned]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#poisoning
#[derive(Default)]
pub struct Dmv;
impl Dmv {
/// Registers a new ID.
///
/// # `None`
///
/// This function returns [`None`] if the ID bucket for scope `S` and base
/// type `T` has not been initialized via [`Self::init`] or
/// [`Self::init_default`]. You can check if the ID type has been
/// initialized via the [`Self::is_init`] function.
///
/// # Panics
///
/// See [`Dmv`] struct docs for panic info.
#[allow(clippy::unused_self)]
#[must_use]
pub fn register<S: Scope, T: RawRepr + Num + Send>(&self) -> Option<DmvId<S, T>> {
let mut lock = MAPS.lock().unwrap();
let scope_map = lock
.get_mut(&TypeId::of::<T>())?
.downcast_mut::<ScopeMap<T>>()
.unwrap();
let val = scope_map.get(TypeId::of::<S>())?;
Some(privId::new(val))
}
/// Creates a new [`Id`] with the given `id_val`.
///
/// This function may be used under contexts where the caller would like the
/// returned [`Id`] to contain a specific value, such as in the case of
/// deserialization.
///
/// # Safety
///
/// It is invalid to pass this function an `id_val` that would allow the
/// resulting [`Id`] to compare equally with another [`Id`] given out by the
/// [`Dmv`]. By calling this function you promise that `id_val` will not
/// break the property that all [`Id`] values (including those not yet
/// created) are unique. [`Dmv::reset`] may be of use in ensuring that the
/// necessary values are reset before calling [`Dmv::register`] again.
///
/// # See Also
///
/// - [`Dmv`] struct documentation
/// - [`Dmv::reset`] associated function documentation
#[allow(clippy::unused_self)]
#[must_use]
pub unsafe fn register_unchecked<S: Scope, T: RawRepr>(&self, id_val: T) -> DmvId<S, T> {
privId::new(id_val)
}
/// Initializes the ID bucket for scope `S` and base type `T` with the
/// default value of [`num_traits::One::one`].
///
/// If an ID bucket exists for `S` and `T` already, this function will do
/// nothing. To always set the ID bucket value to a given `base`, use
/// [`Self::reset`].
///
/// # Panics
///
/// See [`Dmv`] struct docs for panic info.
pub fn init_default<S: Scope, T: RawRepr + Send + Num>(&self) {
self.init::<S, T>(T::one());
}
/// Initializes the ID bucket for scope `S` and base type `T`.
///
/// If an ID bucket exists for `S` and `T` already, this function will do
/// nothing. To always set the ID bucket value to a given `base`, use
/// [`Self::reset`].
///
/// # Panics
///
/// See [`Dmv`] struct docs for panic info.
#[allow(clippy::unused_self)]
pub fn init<S: Scope, T: RawRepr + Send + Num>(&self, base: T) {
let mut lock = MAPS.lock().unwrap();
let map = lock
.entry(TypeId::of::<T>())
.or_insert_with(move || Box::new(ScopeMap::<T>::new()))
.downcast_mut::<ScopeMap<T>>()
.unwrap();
let scope = TypeId::of::<S>();
if !map.has(scope) {
// Safety:
// TODO
unsafe { map.set(scope, base) }
}
}
/// Checks if the ID bucket for scope `S` and base type `T` has been
/// initialized yet.
///
/// # Panics
///
/// See [`Dmv`] struct docs for panic info.
#[allow(clippy::unused_self)]
#[must_use]
pub fn is_init<S: Scope, T: RawRepr + Num>(&self) -> bool {
let maps = MAPS.lock().unwrap();
maps.contains_key(&TypeId::of::<T>())
&& maps
.get(&TypeId::of::<T>())
.unwrap()
.downcast_ref::<ScopeMap<T>>()
.unwrap()
.has(TypeId::of::<S>())
}
/// Resets the value of the ID bucket for scope `S` and base type `T` to
/// `base`.
///
/// # Panics
///
/// See [`Dmv`] struct docs for panic info.
///
/// # Safety
///
/// ## `reset` contract
///
/// It is unsafe to pass this function a `base` that would compare less-than
/// or equal-to (`<=`) any pre-existing [`DmvId`] value with scope `S` and
/// base type `T`.
///
/// ## `reset`-`peek` contract
///
/// See [`Dmv::peek`] method documentation for the full details of this
/// contract.
///
/// This function overwrites the value that would've been returned by a
/// prior call to[`Dmv::peek`], which means that any code that relies on the
/// value returned by [`Dmv::peek`] to be the same value returned by the
/// next call to [`Dmv::register`] will be in an invalid state.
///
/// # See Also
///
/// - [`Dmv`] struct documentation
/// - [`Dmv::peek`] method documentation
#[allow(clippy::unused_self)]
pub unsafe fn reset<S: Scope, T: RawRepr + Send + Num>(&self, base: T) {
let mut lock = MAPS.lock().unwrap();
let scope = lock
.entry(TypeId::of::<T>())
.or_insert_with(move || Box::new(ScopeMap::<T>::new()))
.downcast_mut::<ScopeMap<T>>()
.unwrap();
unsafe {
scope.set(TypeId::of::<S>(), base);
}
}
/// Returns a copy of the value that will be returned by the next call to
/// [`Dmv::register`].
///
/// # Panics
///
/// See [`Dmv`] struct docs for panic info.
///
/// # Safety
///
/// ## `reset`-`peek` contract
///
/// The value that this method returns is representative of the state of the
/// internal machinery at the point in time that the internal mutex is
/// locked during a call to this method. This means that a call to
/// [`Dmv::reset`] would overwriting the value in the that this method
/// would've previously returned a copy of, and thus the value that will be
/// returned by the next call to [`Dmv::register`].
///
/// Any calling code that wants to rely on this value also being the value
/// held internally by the [`DmvId`] returned by the next call to
/// [`Dmv::register`] must also guarantee that [`Dmv::reset`] is not called
/// before [`Dmv::register`] is called or that the previously `peek`'d value
/// should be re-`peek`'d after a call to [`Dmv::reset`].
///
/// # See Also
///
/// - [`Dmv`] struct documentation
/// - [`Dmv::reset`] method documentation
#[allow(clippy::unused_self)]
#[must_use]
pub unsafe fn peek<S: Scope, T: RawRepr + Send + Num>(&self) -> Option<T> {
Some(
MAPS.lock()
.unwrap()
.get(&TypeId::of::<T>())?
.downcast_ref::<ScopeMap<T>>()
.unwrap()
.peek(TypeId::of::<S>())?,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let id1 = Dmv.register::<GlobalScope, usize>().unwrap();
let id2 = Dmv.register::<GlobalScope, usize>().unwrap();
let h1 = id1.handle();
let h2 = id2.handle();
assert_ne!(id1, id2);
assert_ne!(*h1, *h2);
}
#[test]
fn test_reset_peek_contract() {
use crate::aliases::DmvIdSize;
local_scope! { Scope1 };
local_scope! { Scope2 };
//
// Follow the `reset`-`peek` contract
//
Dmv.init::<Scope1, usize>(0);
assert_eq!(unsafe { Dmv.peek::<Scope1, usize>() }.unwrap(), 0);
let _id = Dmv.register::<Scope1, usize>().unwrap();
assert_eq!(unsafe { Dmv.peek::<Scope1, usize>() }.unwrap(), 1);
//
// Break the `reset`-`peek` contract
//
Dmv.init::<Scope2, usize>(0);
let _id = Dmv.register::<Scope2, usize>().unwrap();
// peek what the next ID value will be
let peek = unsafe { Dmv.peek::<Scope2, usize>() }.unwrap();
// change what the next ID value will be
unsafe { Dmv.reset::<Scope2, usize>(3) };
let id: DmvIdSize<Scope2> = Dmv.register::<Scope2, usize>().unwrap();
let handle = <DmvIdSize<Scope2> as Id<Scope2, usize>>::handle(&id);
assert_ne!(peek, *handle);
// `reset` contract is still valid
assert_eq!(*handle, 3);
}
}