devela/data/uid/seq.rs
1// devela::data::uid::seq
2//
3//! Sequential unique IDs.
4//
5
6/// A macro for constructing a unique sequential identifier generator.
7///
8/// It generates the necessary static instances dynamically.
9///
10/// # Example
11/// ```
12/// # use devela::id_seq;
13/// id_seq![AppId1, u8];
14///
15/// assert_eq![AppId1::generated_ids(), 0];
16/// assert_eq![AppId1::remaining_ids(), u8::MAX - 1];
17///
18/// assert_eq![AppId1::new().unwrap().value(), 1];
19/// assert_eq![AppId1::new_unchecked().value(), 2];
20///
21/// // generate all remaining ids.
22/// for _ in 2..u8::MAX {
23/// let _ = AppId1::new_fast_unchecked();
24/// }
25/// assert_eq![AppId1::new_fast(), None];
26/// ```
27///
28/// See also the [id_seq][crate::_info::examples::id_seq] example.
29#[macro_export]
30#[cfg_attr(cargo_primary_package, doc(hidden))]
31macro_rules! id_seq {
32 (
33 // $name: the name of the sequential ID generator. E.g. AppId1.
34 // $prim: the underlying primitive type. E.g. u64.
35 $name:ident,
36 $prim:ty
37 ) => {
38 $crate::paste! {
39 $crate::id_seq![
40 $name, // the name of the ID generator.
41 $name, // the name of the ID generator (as a type).
42 [<$name:upper>], // the name of the static.
43 [<$prim:lower>], // the underlying primitive type.
44 [<Atomic $prim:camel>] // the atomic type in the static.
45 ];
46 }
47 };
48
49 ($name:ident, $tname:ty, $static:ident, $prim:ty, $atomic:ident) => {
50 /// A static atomic counter used to generate unique sequential identifiers of type `$prim`.
51 static $static: core::sync::atomic::$atomic =
52 core::sync::atomic::$atomic::new(<$prim>::MIN + 1);
53
54 #[doc = concat!("A unique sequential identifier `", stringify!($prim), "` generator.")]
55 ///
56 #[doc = concat!("The counter starts at [`", stringify!($prim), "::MIN`]` + 1`,")]
57 /// and increments with each new identifier generated.
58 ///
59 /// The implementation guards against wrap-around
60 #[doc = concat!("after [`", stringify!($prim), "::MAX`],")]
61 /// by returning `None` in the checked methods, or panicking in the unchecked methods.
62 ///
63 /// See also the [`id_seq`] macro.
64 #[derive(Debug)]
65 #[must_use]
66 pub struct $name {
67 id: $prim,
68 }
69
70 impl $name {
71 /* generators */
72
73 #[doc = concat!("Generates some unique `", stringify!($name), "` ID.")]
74 ///
75 /// Alias of [`new_balanced`][Self::new_balanced].
76 ///
77 /// Returns `None` on overflow.
78 #[must_use]
79 pub fn new() -> Option<Self> {
80 Self::new_balanced()
81 }
82 #[doc = concat!("Generates some unique `", stringify!($name), "` ID.")]
83 ///
84 /// Alias of [`new_balanced_unchecked`][Self::new_balanced_unchecked].
85 ///
86 /// # Panics
87 /// Panics on overflow.
88 pub fn new_unchecked() -> Self {
89 Self::new_balanced_unchecked()
90 }
91
92 #[doc = concat!(
93 "Generates some unique `", stringify!($name), "` ID with [`SeqCst`] ordering.")
94 ]
95 ///
96 /// Ensures the strongest memory consistency across all threads,
97 /// even at the cost of performance.
98 ///
99 /// Returns `None` on overflow.
100 ///
101 /// [`SeqCst`]: core::sync::atomic::Ordering::SeqCst
102 #[must_use]
103 pub fn new_strong() -> Option<Self> {
104 Self::new_custom(core::sync::atomic::Ordering::SeqCst)
105 }
106 #[doc = concat!(
107 "Generates a unique `", stringify!($name), "` ID with [`SeqCst`] ordering.")
108 ]
109 ///
110 /// Ensures the strongest memory consistency across all threads,
111 /// even at the cost of performance.
112 ///
113 /// # Panics
114 /// Panics on overflow.
115 ///
116 /// [`SeqCst`]: core::sync::atomic::Ordering::SeqCst
117 pub fn new_strong_unchecked() -> Self {
118 Self::new_custom_unchecked(core::sync::atomic::Ordering::SeqCst)
119 }
120
121 #[doc = concat!(
122 "Generates some unique `", stringify!($name), "` ID with [`AcqRel`] ordering.")
123 ]
124 ///
125 /// Balances performance and memory safety,
126 /// ensuring consistent visibility across threads.
127 ///
128 /// Returns `None` on overflow.
129 ///
130 /// [`AcqRel`]: core::sync::atomic::Ordering::AcqRel
131 #[must_use]
132 pub fn new_balanced() -> Option<Self> {
133 Self::new_custom(core::sync::atomic::Ordering::AcqRel)
134 }
135 #[doc = concat!(
136 "Generates a unique `", stringify!($name), "` ID with [`AcqRel`] ordering.")
137 ]
138 ///
139 /// Balances performance and memory safety,
140 /// ensuring consistent visibility across threads.
141 ///
142 /// # Panics
143 /// Panics on overflow.
144 ///
145 /// [`AcqRel`]: core::sync::atomic::Ordering::AcqRel
146 pub fn new_balanced_unchecked() -> Self {
147 Self::new_custom_unchecked(core::sync::atomic::Ordering::AcqRel)
148 }
149
150 #[doc = concat!(
151 "Generates some unique `", stringify!($name), "` ID with [`Relaxed`] ordering.")
152 ]
153 ///
154 /// Offers maximum performance in low-contention scenarios
155 /// where memory ordering is not a concern.
156 ///
157 /// Returns `None` on overflow.
158 ///
159 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
160 #[must_use]
161 pub fn new_fast() -> Option<Self> {
162 Self::new_custom(core::sync::atomic::Ordering::Relaxed)
163 }
164 #[doc = concat!(
165 "Generates a unique `", stringify!($name), "` ID with [`Relaxed`] ordering.")
166 ]
167 ///
168 /// Offers maximum performance in low-contention scenarios
169 /// where memory ordering is not a concern.
170 ///
171 /// # Panics
172 /// Panics on overflow.
173 ///
174 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
175 pub fn new_fast_unchecked() -> Self {
176 Self::new_custom_unchecked(core::sync::atomic::Ordering::Relaxed)
177 }
178
179 /* iterators */
180
181 /// Iterator over generated IDs with `SeqCst` ordering.
182 ///
183 /// Ensures the strongest memory consistency across all threads,
184 /// even at the cost of performance.
185 ///
186 /// [`SeqCst`]: core::sync::atomic::Ordering::SeqCst
187 pub fn iter_strong() -> impl Iterator<Item = $name> {
188 core::iter::from_fn(|| Self::new_strong())
189 }
190 /// Iterator over generated IDs with `SeqCst` ordering.
191 ///
192 /// Ensures the strongest memory consistency across all threads,
193 /// even at the cost of performance.
194 ///
195 /// # Panics
196 /// Panics on overflow.
197 ///
198 /// [`SeqCst`]: core::sync::atomic::Ordering::SeqCst
199 pub fn iter_strong_unchecked() -> impl Iterator<Item = $name> {
200 core::iter::from_fn(|| Some(Self::new_strong_unchecked()))
201 }
202
203 /// Iterator over generated IDs with `AcqRel` ordering.
204 ///
205 /// Balances performance and memory safety,
206 /// ensuring consistent visibility across threads.
207 ///
208 /// [`AcqRel`]: core::sync::atomic::Ordering::AcqRel
209 pub fn iter_balanced() -> impl Iterator<Item = $name> {
210 core::iter::from_fn(|| Self::new_balanced())
211 }
212 /// Iterator over generated IDs with `AcqRel` ordering.
213 ///
214 /// Balances performance and memory safety,
215 /// ensuring consistent visibility across threads.
216 ///
217 /// # Panics
218 /// Panics on overflow.
219 ///
220 /// [`AcqRel`]: core::sync::atomic::Ordering::AcqRel
221 pub fn iter_balanced_unchecked() -> impl Iterator<Item = $name> {
222 core::iter::from_fn(|| Some(Self::new_balanced_unchecked()))
223 }
224
225 /// Iterator over generated IDs with `Relaxed` ordering.
226 ///
227 /// Offers maximum performance in low-contention scenarios
228 /// where memory ordering is not a concern.
229 ///
230 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
231 pub fn iter_fast() -> impl Iterator<Item = $name> {
232 core::iter::from_fn(|| Self::new_fast())
233 }
234 /// Iterator over generated IDs with `Relaxed` ordering.
235 ///
236 /// Offers maximum performance in low-contention scenarios
237 /// where memory ordering is not a concern.
238 ///
239 /// # Panics
240 /// Panics on overflow.
241 ///
242 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
243 pub fn iter_fast_unchecked() -> impl Iterator<Item = $name> {
244 core::iter::from_fn(|| Some(Self::new_fast_unchecked()))
245 }
246
247 /* queries */
248
249 /// Returns the underlying unique ID value
250 #[doc = concat!("as a `", stringify!($prim), "`.")]
251 ///
252 /// The value is guaranteed to be a valid sequential identifier, from
253 #[doc = concat!("`", stringify!($prim) ,"::MIN` to `", stringify!($prim), "::MAX`.")]
254 pub fn value(&self) -> $prim {
255 self.id
256 }
257
258 /// Returns the number of IDs generated so far.
259 ///
260 /// Alias of [`generated_ids_balanced`][Self::generated_ids_balanced].
261 #[must_use]
262 pub fn generated_ids() -> $prim {
263 Self::generated_ids_balanced()
264 }
265 /// Returns the number of IDs generated so far with [`SeqCst`] ordering.
266 ///
267 /// Ensures the strongest memory consistency across all threads,
268 /// even at the cost of performance.
269 ///
270 /// [`SeqCst`]: core::sync::atomic::Ordering::SeqCst
271 #[must_use]
272 pub fn generated_ids_strong() -> $prim {
273 let current_id = $static.load(core::sync::atomic::Ordering::SeqCst);
274 current_id - 1
275 }
276 /// Returns the number of IDs generated so far with [`Acquire`] ordering.
277 ///
278 /// Balances performance and memory safety,
279 /// ensuring consistent visibility across threads.
280 ///
281 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
282 #[must_use]
283 pub fn generated_ids_balanced() -> $prim {
284 let current_id = $static.load(core::sync::atomic::Ordering::Acquire);
285 current_id - 1
286 }
287 /// Returns the number of IDs generated so far with [`Relaxed`] ordering.
288 ///
289 /// Offers maximum performance in low-contention scenarios
290 /// where memory ordering is not a concern.
291 ///
292 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
293 #[must_use]
294 pub fn generated_ids_fast() -> $prim {
295 let current_id = $static.load(core::sync::atomic::Ordering::Relaxed);
296 current_id - 1
297 }
298
299 /// Returns the number of remaining IDs.
300 ///
301 /// Alias of [`remaining_ids_balanced`][Self::remaining_ids_balanced].
302 #[must_use]
303 pub fn remaining_ids() -> $prim {
304 Self::remaining_ids_balanced()
305 }
306 /// Returns the number of remaining IDs with [`SeqCst`] ordering.
307 ///
308 /// Ensures the strongest memory consistency across all threads,
309 /// even at the cost of performance.
310 ///
311 /// [`SeqCst`]: core::sync::atomic::Ordering::SeqCst
312 #[must_use]
313 pub fn remaining_ids_strong() -> $prim {
314 let current_id = $static.load(core::sync::atomic::Ordering::SeqCst);
315 <$prim>::MAX - current_id
316 }
317 /// Returns the number of remaining IDs with [`Acquire`] ordering.
318 ///
319 /// Balances performance and memory safety,
320 /// ensuring consistent visibility across threads.
321 ///
322 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
323 #[must_use]
324 pub fn remaining_ids_balanced() -> $prim {
325 let current_id = $static.load(core::sync::atomic::Ordering::Acquire);
326 <$prim>::MAX - current_id
327 }
328 /// Returns the number of remaining IDs with [`Relaxed`] ordering.
329 ///
330 /// Offers maximum performance in low-contention scenarios
331 /// where memory ordering is not a concern.
332 ///
333 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
334 #[must_use]
335 pub fn remaining_ids_fast() -> $prim {
336 let current_id = $static.load(core::sync::atomic::Ordering::Relaxed);
337 <$prim>::MAX - current_id
338 }
339
340 /* private helpers */
341
342 fn new_custom(ordering: core::sync::atomic::Ordering) -> Option<Self> {
343 let id = $static.fetch_add(1, ordering);
344 if id == <$prim>::MIN {
345 Self::none_on_overflow()
346 } else {
347 Some(Self { id })
348 }
349 }
350 #[cold] #[rustfmt::skip]
351 fn none_on_overflow() -> Option<Self> { None }
352
353 fn new_custom_unchecked(ordering: core::sync::atomic::Ordering) -> Self {
354 let id = $static.fetch_add(1, ordering);
355 if id == <$prim>::MIN {
356 Self::panic_on_overflow();
357 }
358 Self { id }
359 }
360 #[cold] #[rustfmt::skip]
361 fn panic_on_overflow() -> ! { panic!("ID counter overflowed"); }
362 }
363
364 /* trait impls */
365
366 impl From<$name> for $prim {
367 fn from(from: $name) -> $prim {
368 from.value()
369 }
370 }
371
372 impl core::hash::Hash for $name {
373 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
374 self.id.hash(state);
375 }
376 }
377
378 impl PartialEq for $name {
379 fn eq(&self, other: &Self) -> bool {
380 self.id == other.id
381 }
382 }
383 impl Eq for $name {}
384
385 impl PartialOrd for $name {
386 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
387 Some(self.id.cmp(&other.id))
388 }
389 }
390 impl Ord for $name {
391 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
392 self.id.cmp(&other.id)
393 }
394 }
395 };
396}
397#[doc(inline)]
398pub use id_seq;
399
400mod test {
401 #[allow(unused_imports)] // BUG:compiler doesn't detect use of ExtAny::type_of
402 use crate::{assert_eq_all, id_seq, ExtAny};
403
404 #[test]
405 fn id_seq_start_uniqueness_end() {
406 id_seq![TestIdSeqU8a, u8];
407 id_seq![TestIdSeqU8b, u8];
408 id_seq![TestIdSeqI8, i8];
409
410 // unsigned starts at 1 (MIN+1)
411 let u8a_id1 = TestIdSeqU8a::new().unwrap();
412 let u8b_id1 = TestIdSeqU8b::new().unwrap();
413
414 // types are different, values can be the same
415 assert_ne![u8a_id1.type_of(), u8b_id1.type_of()];
416 assert_eq_all![1, u8a_id1.value(), u8b_id1.value()];
417
418 let u8a_id2 = TestIdSeqU8a::new().unwrap();
419 assert_eq![2, u8a_id2.value()];
420
421 // signed starts at MIN+1
422 let i8_id1 = TestIdSeqI8::new().unwrap();
423 let i8_id2 = TestIdSeqI8::new().unwrap();
424 assert_eq![i8::MIN + 1, i8_id1.value()];
425 assert_eq![i8::MIN + 2, i8_id2.value()];
426
427 // generate all remaining ids
428 for _ in 2..u8::MAX {
429 let _ = TestIdSeqU8a::new_fast_unchecked();
430 let _ = TestIdSeqI8::new_fast_unchecked();
431 }
432 // check wrapping prevention
433 assert_eq![TestIdSeqU8a::new_fast(), None];
434 assert_eq![TestIdSeqI8::new_fast(), None];
435 }
436
437 #[test]
438 #[cfg(feature = "alloc")]
439 fn id_seq_iter() {
440 use crate::Vec;
441
442 id_seq![TestIdSeqU8Iter, u8];
443
444 let ids: Vec<_> = TestIdSeqU8Iter::iter_fast().take(10).collect();
445 // First 10 IDs should start at 1 and end at 10
446 let expected_ids: Vec<u8> = (1..=10).collect();
447 assert_eq!(ids.iter().map(|id| id.value()).collect::<Vec<_>>(), expected_ids);
448
449 let ids: Vec<_> = TestIdSeqU8Iter::iter_balanced().take(10).collect();
450 // next 10 IDs should start at 11 and end at 20
451 let expected_ids: Vec<u8> = (11..=20).collect();
452 assert_eq!(ids.iter().map(|id| id.value()).collect::<Vec<_>>(), expected_ids);
453 }
454
455 #[test]
456 #[cfg(feature = "alloc")]
457 fn id_seq_iter_stops_at_max() {
458 use crate::Vec;
459
460 id_seq![TestIdSeqU8IterStops, u8];
461 type IdGen = TestIdSeqU8IterStops;
462
463 // move the id counter close to the maximum value
464 let _: Vec<_> = IdGen::iter_fast().take(252).collect();
465
466 // take the rest of the ids
467 let ids: Vec<_> = IdGen::iter_fast().collect();
468
469 let expected_ids = Vec::from([253, 254, 255]);
470 assert_eq!(ids.iter().map(|id| id.value()).collect::<Vec<_>>(), expected_ids);
471 }
472
473 #[test]
474 #[cfg(feature = "std")]
475 fn id_seq_iter_panics_on_overflow() {
476 use std::panic::catch_unwind;
477
478 id_seq![TestIdSeqU8IterPanics, u8];
479 type IdGen = TestIdSeqU8IterPanics;
480
481 // move the id counter to the maximum value
482 let _: Vec<_> = IdGen::iter_fast_unchecked().take(255).collect();
483
484 // Expect a panic on overflow
485 let result = catch_unwind(|| {
486 let _ids: Vec<_> = IdGen::iter_fast_unchecked().take(1).collect();
487 });
488 assert!(result.is_err(), "Expected panic due to overflow, but no panic occurred");
489 }
490}