#![no_std]
#![warn(
future_incompatible,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
rust_2018_compatibility,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unsafe_op_in_unsafe_fn,
unstable_features,
unused_import_braces,
unused_qualifications,
unused_results
)]
#[cfg(test)]
#[macro_use]
extern crate std;
use core::fmt::Debug;
use core::fmt::Display;
use core::fmt::Formatter;
use core::fmt::Result;
use core::marker::PhantomData;
use core::num::NonZeroU16;
use core::num::NonZeroU32;
use core::num::NonZeroU64;
use core::num::NonZeroU8;
use core::num::NonZeroUsize;
use core::sync::atomic::AtomicU16;
use core::sync::atomic::AtomicU32;
use core::sync::atomic::AtomicU64;
use core::sync::atomic::AtomicU8;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
macro_rules! IdImpl {
( $(#[$docs:meta])* struct $name: ident, $int_type:ty, $non_zero_type:ty, $atomic_type: ty ) => {
$(#[$docs])*
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct $name<T> {
id: $non_zero_type,
phantom: PhantomData<T>,
}
impl<T> $name<T> {
#[inline]
pub unsafe fn new_unchecked(id: $int_type) -> Self {
Self {
id: unsafe { <$non_zero_type>::new_unchecked(id) },
phantom: PhantomData,
}
}
#[inline]
pub fn new() -> Self {
static NEXT_ID: $atomic_type = <$atomic_type>::new(1);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
assert_ne!(
id, 0,
"overflow detected; please use a larger integer to or reconsider your use case"
);
unsafe { Self::new_unchecked(id) }
}
#[inline]
pub fn get(self) -> $int_type {
self.id.get()
}
}
impl<T> Default for $name<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T> Debug for $name<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_tuple(stringify!($name)).field(&self.id).finish()
}
}
impl<T> Display for $name<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.id)
}
}
}
}
IdImpl! {
struct Id, usize, NonZeroUsize, AtomicUsize
}
IdImpl! {
struct IdU8, u8, NonZeroU8, AtomicU8
}
IdImpl! {
struct IdU16, u16, NonZeroU16, AtomicU16
}
IdImpl! {
struct IdU32, u32, NonZeroU32, AtomicU32
}
IdImpl! {
struct IdU64, u64, NonZeroU64, AtomicU64
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
use std::collections::HashSet;
use std::iter::FromIterator;
use std::mem::size_of;
use std::mem::size_of_val;
use std::thread::spawn;
use std::vec::Vec;
type TestId = Id<u32>;
#[test]
fn unique_id_increases() {
let id1 = TestId::new();
let id2 = TestId::new();
assert!(id2 > id1);
assert!(id2.get() > id1.get());
}
#[test]
fn thread_safety() {
#[allow(clippy::needless_collect)]
fn test<T>()
where
T: FromIterator<TestId> + IntoIterator,
{
let handles = (0..100).map(|_| spawn(TestId::new)).collect::<Vec<_>>();
let result = handles
.into_iter()
.map(|x| x.join().unwrap())
.collect::<T>();
assert_eq!(result.into_iter().count(), 100);
}
test::<BTreeSet<TestId>>();
test::<HashSet<TestId>>();
}
#[test]
fn debug() {
let id = unsafe { TestId::new_unchecked(42) };
assert_eq!(format!("{:?}", id), "Id(42)");
type TestId2 = IdU16<()>;
let id = unsafe { TestId2::new_unchecked(1337) };
assert_eq!(format!("{:?}", id), "IdU16(1337)");
}
#[test]
fn display() {
let id = unsafe { TestId::new_unchecked(43) };
assert_eq!(format!("{}", id), "43");
}
#[test]
fn size() {
let id = Some(TestId::new());
assert_eq!(size_of_val(&id), size_of::<TestId>());
assert_eq!(size_of::<TestId>(), size_of::<usize>());
assert_eq!(size_of::<IdU8<()>>(), size_of::<u8>());
assert_eq!(size_of::<IdU16<()>>(), size_of::<u16>());
assert_eq!(size_of::<IdU32<()>>(), size_of::<u32>());
assert_eq!(size_of::<IdU64<()>>(), size_of::<u64>());
}
#[test]
#[should_panic(expected = "overflow detected")]
fn overflow() {
(0..256).for_each(|_| {
let _ = IdU8::<()>::new();
});
}
}