Skip to main content

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}