uid/lib.rs
1// lib.rs
2
3// *************************************************************************
4// * Copyright (C) 2018-2021 Daniel Mueller (deso@posteo.net) *
5// * *
6// * This program is free software: you can redistribute it and/or modify *
7// * it under the terms of the GNU General Public License as published by *
8// * the Free Software Foundation, either version 3 of the License, or *
9// * (at your option) any later version. *
10// * *
11// * This program is distributed in the hope that it will be useful, *
12// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14// * GNU General Public License for more details. *
15// * *
16// * You should have received a copy of the GNU General Public License *
17// * along with this program. If not, see <http://www.gnu.org/licenses/>. *
18// *************************************************************************
19
20#![no_std]
21#![warn(
22 future_incompatible,
23 missing_copy_implementations,
24 missing_debug_implementations,
25 missing_docs,
26 rust_2018_compatibility,
27 rust_2018_idioms,
28 trivial_casts,
29 trivial_numeric_casts,
30 unsafe_op_in_unsafe_fn,
31 unstable_features,
32 unused_import_braces,
33 unused_qualifications,
34 unused_results
35)]
36
37//! A crate providing in-memory IDs. Among others, the IDs are
38//! guaranteed to be unique, even when created on different threads.
39
40#[cfg(test)]
41#[macro_use]
42extern crate std;
43
44use core::fmt::Debug;
45use core::fmt::Display;
46use core::fmt::Formatter;
47use core::fmt::Result;
48use core::marker::PhantomData;
49use core::num::NonZeroU16;
50use core::num::NonZeroU32;
51use core::num::NonZeroU64;
52use core::num::NonZeroU8;
53use core::num::NonZeroUsize;
54use core::sync::atomic::AtomicU16;
55use core::sync::atomic::AtomicU32;
56use core::sync::atomic::AtomicU64;
57use core::sync::atomic::AtomicU8;
58use core::sync::atomic::AtomicUsize;
59use core::sync::atomic::Ordering;
60
61
62macro_rules! IdImpl {
63 ( $(#[$docs:meta])* struct $name: ident, $int_type:ty, $non_zero_type:ty, $atomic_type: ty ) => {
64 $(#[$docs])*
65 #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
66 #[repr(transparent)]
67 pub struct $name<T> {
68 id: $non_zero_type,
69 phantom: PhantomData<T>,
70 }
71
72 impl<T> $name<T> {
73 /// Create a new ID using the given value.
74 ///
75 /// # Panics
76 /// This constructor panics if an overflow of the underlying
77 /// counter occurred.
78 ///
79 /// # Safety
80 /// - `id` must not be zero
81 /// - `id` should be unique with respect to other IDs created for this
82 /// `T` to preserve the invariant that IDs are unique
83 #[inline]
84 pub unsafe fn new_unchecked(id: $int_type) -> Self {
85 Self {
86 id: unsafe { <$non_zero_type>::new_unchecked(id) },
87 phantom: PhantomData,
88 }
89 }
90
91 /// Create a new unique ID.
92 ///
93 /// # Panics
94 /// This constructor panics if an overflow of the underlying
95 /// counter occurred.
96 #[inline]
97 pub fn new() -> Self {
98 static NEXT_ID: $atomic_type = <$atomic_type>::new(1);
99
100 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
101 assert_ne!(
102 id, 0,
103 "overflow detected; please use a larger integer to or reconsider your use case"
104 );
105
106 // SAFETY: The provided ID cannot be 0 (unless we overflow, in which
107 // case we have other problems). We ensure uniqueness
108 // because we increment IDs and this is the only constructor
109 // for ID objects.
110 unsafe { Self::new_unchecked(id) }
111 }
112
113 /// Retrieve the underlying integer value.
114 #[inline]
115 pub fn get(self) -> $int_type {
116 self.id.get()
117 }
118 }
119
120 impl<T> Default for $name<T> {
121 /// Create a new unique ID.
122 #[inline]
123 fn default() -> Self {
124 Self::new()
125 }
126 }
127
128 impl<T> Debug for $name<T> {
129 #[inline]
130 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
131 f.debug_tuple(stringify!($name)).field(&self.id).finish()
132 }
133 }
134
135 impl<T> Display for $name<T> {
136 /// Format the ID with the given formatter.
137 #[inline]
138 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
139 write!(f, "{}", self.id)
140 }
141 }
142 }
143}
144
145
146IdImpl! {
147 /// A struct representing IDs usable for various purposes.
148 ///
149 /// Except for [`Debug`] and [`Display`] which are implemented
150 /// unconditionally, the type will only implement [`Clone`],
151 /// [`Copy`], [`Eq`], [`Ord`], [`PartialEq`], [`PartialOrd`], and
152 /// [`Hash`] if the provided `T` implements them.
153 ///
154 /// # Examples
155 ///
156 /// A commonly seen pattern for creating of a type `Id` that is unique
157 /// may look as follows:
158 /// ```rust
159 /// use uid::Id as IdT;
160 ///
161 /// #[derive(Copy, Clone, Eq, PartialEq)]
162 /// struct T(());
163 ///
164 /// type Id = IdT<T>;
165 ///
166 /// let id1 = Id::new();
167 /// let id2 = Id::new();
168 ///
169 /// assert_ne!(id1, id2)
170 /// ```
171 ///
172 /// In this example the type `T` is just an arbitrary type, but it
173 /// allows us to create distinct ID types. For example, when another ID
174 /// type is required for a different purpose, that can be easily
175 /// created:
176 /// ```rust
177 /// # use uid::Id as IdT;
178 /// # #[derive(Copy, Clone)]
179 /// # struct T(());
180 /// # type Id = IdT<T>;
181 /// #[derive(Copy, Clone)]
182 /// struct U(());
183 ///
184 /// type Key = IdT<U>;
185 ///
186 /// // `Key` and `Id` are fundamentally different types, with no
187 /// // allowed interaction between each other. That is, Rust's type
188 /// // system will prevent accidental usage of one in place of the
189 /// // other. The same can be said about the relationship to built-in
190 /// // numeric types such as `usize` or `u64`.
191 /// ```
192 struct Id, usize, NonZeroUsize, AtomicUsize
193}
194IdImpl! {
195 /// A struct representing IDs usable for various purposes using an eight
196 /// bit wide unsigned integer.
197 ///
198 /// Please see the [`Id`] type for more general information and usage
199 /// examples.
200 struct IdU8, u8, NonZeroU8, AtomicU8
201}
202IdImpl! {
203 /// A struct representing IDs usable for various purposes using an 16
204 /// bit wide unsigned integer.
205 ///
206 /// Please see the [`Id`] type for more general information and usage
207 /// examples.
208 struct IdU16, u16, NonZeroU16, AtomicU16
209}
210IdImpl! {
211 /// A struct representing IDs usable for various purposes using an 32
212 /// bit wide unsigned integer.
213 ///
214 /// Please see the [`Id`] type for more general information and usage
215 /// examples.
216 struct IdU32, u32, NonZeroU32, AtomicU32
217}
218IdImpl! {
219 /// A struct representing IDs usable for various purposes using an 64
220 /// bit wide unsigned integer.
221 ///
222 /// Please see the [`Id`] type for more general information and usage
223 /// examples.
224 struct IdU64, u64, NonZeroU64, AtomicU64
225}
226
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 use std::collections::BTreeSet;
233 use std::collections::HashSet;
234 use std::iter::FromIterator;
235 use std::mem::size_of;
236 use std::mem::size_of_val;
237 use std::thread::spawn;
238 use std::vec::Vec;
239
240
241 type TestId = Id<u32>;
242
243
244 /// Make sure that [`Id`] values are increasing.
245 #[test]
246 fn unique_id_increases() {
247 let id1 = TestId::new();
248 let id2 = TestId::new();
249
250 assert!(id2 > id1);
251 assert!(id2.get() > id1.get());
252 }
253
254 /// Test that [`Id`] objects created on different threads preserve
255 /// uniqueness invariant.
256 #[test]
257 fn thread_safety() {
258 #[allow(clippy::needless_collect)]
259 fn test<T>()
260 where
261 T: FromIterator<TestId> + IntoIterator,
262 {
263 let handles = (0..100).map(|_| spawn(TestId::new)).collect::<Vec<_>>();
264
265 let result = handles
266 .into_iter()
267 .map(|x| x.join().unwrap())
268 .collect::<T>();
269
270 assert_eq!(result.into_iter().count(), 100);
271 }
272
273 // Run the test both with a `BTreeSet` and `HashSet` to test the
274 // implementations of the traits they require.
275 test::<BTreeSet<TestId>>();
276 test::<HashSet<TestId>>();
277 }
278
279 /// Check that the [`Debug`] implementation of [`Id`] works as
280 /// expected.
281 #[test]
282 fn debug() {
283 let id = unsafe { TestId::new_unchecked(42) };
284 assert_eq!(format!("{:?}", id), "Id(42)");
285
286 type TestId2 = IdU16<()>;
287
288 let id = unsafe { TestId2::new_unchecked(1337) };
289 assert_eq!(format!("{:?}", id), "IdU16(1337)");
290 }
291
292 /// Check that the [`Display`] implementation of [`Id`] works as
293 /// expected.
294 #[test]
295 fn display() {
296 let id = unsafe { TestId::new_unchecked(43) };
297 assert_eq!(format!("{}", id), "43");
298 }
299
300 /// Make sure that our [`Id`] type has expected memory layout and
301 /// size.
302 #[test]
303 fn size() {
304 let id = Some(TestId::new());
305 assert_eq!(size_of_val(&id), size_of::<TestId>());
306 assert_eq!(size_of::<TestId>(), size_of::<usize>());
307
308 assert_eq!(size_of::<IdU8<()>>(), size_of::<u8>());
309 assert_eq!(size_of::<IdU16<()>>(), size_of::<u16>());
310 assert_eq!(size_of::<IdU32<()>>(), size_of::<u32>());
311 assert_eq!(size_of::<IdU64<()>>(), size_of::<u64>());
312 }
313
314 /// Verify that we panic when we create more ID objects than the
315 /// underlying integer type can represent.
316 #[test]
317 #[should_panic(expected = "overflow detected")]
318 fn overflow() {
319 (0..256).for_each(|_| {
320 let _ = IdU8::<()>::new();
321 });
322 }
323}