registry_io/handler_id.rs
1//! Opaque handler identifier returned by registration.
2//!
3//! Each registration produces a [`HandlerId`] that the caller can use to
4//! later unregister the handler. Ids are unique within the registry that
5//! issued them and are not portable across registries.
6
7use core::fmt;
8use core::sync::atomic::{AtomicU64, Ordering};
9
10/// Opaque identifier for a registered handler.
11///
12/// Returned by [`SyncRegistry::register`](crate::SyncRegistry::register) and
13/// its priority/guard variants, accepted by
14/// [`SyncRegistry::unregister`](crate::SyncRegistry::unregister).
15///
16/// `HandlerId` is `Copy` and cheap to compare. The internal numeric
17/// representation is intentionally opaque: callers should not rely on
18/// specific values or any ordering between ids.
19///
20/// # Uniqueness
21///
22/// Ids are unique **within the registry that issued them**, for the lifetime
23/// of that registry. Two registries may issue the same numeric id; never use
24/// an id with a registry other than the one that returned it.
25///
26/// # Examples
27///
28/// ```
29/// use registry_io::SyncRegistry;
30///
31/// let registry: SyncRegistry<u32> = SyncRegistry::new();
32/// let id_a = registry.register(|_| {});
33/// let id_b = registry.register(|_| {});
34///
35/// assert_ne!(id_a, id_b);
36/// assert!(registry.unregister(id_a));
37/// assert!(registry.unregister(id_b));
38/// ```
39#[derive(Copy, Clone, PartialEq, Eq, Hash)]
40pub struct HandlerId(u64);
41
42impl HandlerId {
43 /// Construct a [`HandlerId`] from a raw integer.
44 ///
45 /// Internal: only the id generator should call this. The numeric domain
46 /// is otherwise opaque.
47 #[inline]
48 pub(crate) const fn from_raw(raw: u64) -> Self {
49 Self(raw)
50 }
51
52 /// Returns the raw numeric value backing this id.
53 ///
54 /// The exact numeric domain is unspecified and may change between
55 /// releases. This is provided for diagnostic use only (logging,
56 /// debugging); do not rely on specific values or arithmetic over them.
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// use registry_io::SyncRegistry;
62 ///
63 /// let registry: SyncRegistry<()> = SyncRegistry::new();
64 /// let id = registry.register(|_| {});
65 /// let raw = id.as_u64();
66 /// assert!(raw > 0);
67 /// ```
68 #[inline]
69 #[must_use]
70 pub const fn as_u64(self) -> u64 {
71 self.0
72 }
73}
74
75impl fmt::Debug for HandlerId {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "HandlerId({})", self.0)
78 }
79}
80
81impl fmt::Display for HandlerId {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 fmt::Display::fmt(&self.0, f)
84 }
85}
86
87/// Atomic counter that issues unique [`HandlerId`]s for one registry.
88///
89/// Uses `Ordering::Relaxed` because uniqueness only requires atomicity of
90/// the fetch-and-add itself; no other memory state is ordered against the
91/// id allocation.
92#[derive(Debug)]
93pub(crate) struct HandlerIdGenerator {
94 next: AtomicU64,
95}
96
97impl HandlerIdGenerator {
98 /// Construct a new generator. The first id issued will be `1`.
99 #[inline]
100 pub(crate) const fn new() -> Self {
101 Self {
102 next: AtomicU64::new(1),
103 }
104 }
105
106 /// Issue the next unique id.
107 #[inline]
108 pub(crate) fn next(&self) -> HandlerId {
109 let raw = self.next.fetch_add(1, Ordering::Relaxed);
110 HandlerId::from_raw(raw)
111 }
112}
113
114#[cfg(test)]
115#[allow(clippy::unwrap_used, clippy::expect_used)]
116mod tests {
117 use super::{HandlerId, HandlerIdGenerator};
118
119 #[test]
120 fn debug_format_includes_value() {
121 let id = HandlerId::from_raw(42);
122 assert_eq!(format!("{id:?}"), "HandlerId(42)");
123 }
124
125 #[test]
126 fn display_format_is_bare_number() {
127 let id = HandlerId::from_raw(42);
128 assert_eq!(format!("{id}"), "42");
129 }
130
131 #[test]
132 fn ids_are_copy_eq_hash() {
133 let a = HandlerId::from_raw(7);
134 let b = a;
135 assert_eq!(a, b);
136 let mut set = std::collections::HashSet::new();
137 let _ = set.insert(a);
138 assert!(set.contains(&b));
139 }
140
141 #[test]
142 fn generator_produces_unique_ascending_ids() {
143 let generator = HandlerIdGenerator::new();
144 let a = generator.next();
145 let b = generator.next();
146 let c = generator.next();
147 assert_eq!(a.as_u64(), 1);
148 assert_eq!(b.as_u64(), 2);
149 assert_eq!(c.as_u64(), 3);
150 assert_ne!(a, b);
151 assert_ne!(b, c);
152 }
153
154 #[test]
155 fn generator_is_thread_safe_and_unique() {
156 use std::sync::Arc;
157 use std::thread;
158
159 let generator = Arc::new(HandlerIdGenerator::new());
160 let mut handles = Vec::new();
161 for _ in 0..8 {
162 let g = Arc::clone(&generator);
163 handles.push(thread::spawn(move || {
164 let mut ids = Vec::with_capacity(1000);
165 for _ in 0..1000 {
166 ids.push(g.next());
167 }
168 ids
169 }));
170 }
171 let mut all = Vec::with_capacity(8 * 1000);
172 for h in handles {
173 let mut ids = h.join().expect("worker thread did not panic");
174 all.append(&mut ids);
175 }
176 let unique: std::collections::HashSet<_> = all.iter().copied().collect();
177 assert_eq!(unique.len(), all.len());
178 }
179}