pin_list/
id.rs

1//! Unique IDs.
2
3use crate::util::ConstFnBounds;
4use core::fmt::Debug;
5
6/// A marker trait for any type that functions as an ID.
7///
8/// # Safety
9///
10/// It must not be possible to create an arbitrary ID that is equal to one that already exists
11/// without cloning that exact ID.
12pub unsafe trait Id: Sized + Copy + PartialEq + Eq + Debug {}
13
14/// A wrapper around an ID that asserts it is unique.
15///
16/// This takes away the implementation of [`Copy`] and [`Clone`] for an ID and forbids access to
17/// the underlying ID.
18#[derive(Debug, PartialEq, Eq)]
19pub struct Unique<I: Id> {
20    id: I,
21}
22
23impl<I> Unique<I>
24where
25    <I as ConstFnBounds>::Type: Id,
26{
27    /// Create a new `Unique`, asserting that the given ID contained within is unique.
28    ///
29    /// # Safety
30    ///
31    /// The given `id` must be unique at the time of calling this function.
32    pub const unsafe fn new(id: I) -> Self {
33        Self { id }
34    }
35
36    /// Take the inner ID out of this [`Unique`], taking away the uniqueness guarantee.
37    #[must_use]
38    pub const fn into_inner(self) -> I {
39        self.id
40    }
41}
42
43// SAFETY: `Unique<I>` functions as a `SyncWrapper`
44unsafe impl<I: Id> Sync for Unique<I> {}
45
46#[cfg(target_has_atomic = "64")]
47mod checked {
48    use super::Id;
49    use super::Unique;
50    use core::num::NonZeroU64;
51    use core::sync::atomic;
52    use core::sync::atomic::AtomicU64;
53
54    /// An allocator of IDs that uses a global atomic `u64` counter.
55    ///
56    /// This type is only available on platforms with 64-bit atomics.
57    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
58    pub struct Checked(NonZeroU64);
59
60    impl Checked {
61        /// Allocate a new ID.
62        #[must_use]
63        pub fn new() -> Unique<Self> {
64            static COUNTER: AtomicU64 = AtomicU64::new(1);
65            const MAX_ID: u64 = u64::MAX >> 1;
66
67            // Use Relaxed because there is no data that depends on this counter.
68            let id = COUNTER.fetch_add(1, atomic::Ordering::Relaxed);
69
70            // Ensure overflows don't happen. Abort instead of panicking so it can't be recovered
71            // from.
72            if id >= MAX_ID {
73                crate::util::abort();
74            }
75
76            // SAFETY: `COUNTER` starts at one and the above `assert!` ensures that it never
77            // overflows.
78            let id = Self(unsafe { NonZeroU64::new_unchecked(id) });
79
80            // SAFETY: The counter only increments and never overflows.
81            unsafe { Unique::new(id) }
82        }
83    }
84
85    // SAFETY: `new` can never return two `u64`s with the same value.
86    unsafe impl Id for Checked {}
87}
88#[cfg(target_has_atomic = "64")]
89pub use checked::Checked;
90
91mod unchecked {
92    use super::Id;
93    use super::Unique;
94
95    /// An unchecked ID that leaves all the invariants up to the user. This is zero-cost, but
96    /// `unsafe` to use.
97    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
98    #[non_exhaustive]
99    pub struct Unchecked;
100
101    impl Unchecked {
102        /// Create a new [`Unchecked`] ID.
103        ///
104        /// # Safety
105        ///
106        /// The returned ID must not be compared against any other `Unchecked` IDs that originated
107        /// from a different call to this function.
108        #[must_use]
109        pub const unsafe fn new() -> Unique<Self> {
110            // SAFETY: Ensured by caller
111            unsafe { Unique::new(Self) }
112        }
113    }
114
115    // SAFETY: Ensured by caller in `Unchecked::new`
116    unsafe impl Id for Unchecked {}
117}
118pub use unchecked::Unchecked;
119
120#[cfg(target_has_atomic = "64")]
121mod debug_checked {
122    use super::Id;
123    use super::Unique;
124    use crate::id;
125
126    /// Equivalent to [`id::Checked`] when `debug_assertions` are enabled, but equivalent to
127    /// [`id::Unchecked`] in release.
128    ///
129    /// This type is only available on platforms with 64-bit atomics.
130    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
131    pub struct DebugChecked {
132        #[cfg(debug_assertions)]
133        checked: id::Checked,
134    }
135
136    impl DebugChecked {
137        /// Create a new [`DebugChecked`]. With `debug_assertions` enabled, this will increment a
138        /// global atomic counter. In release, this is a no-op.
139        ///
140        /// # Safety
141        ///
142        /// The returned ID must not be compared against any other `DebugChecked` IDs that
143        /// originated from a different call to this function.
144        ///
145        /// Note that this function is completely safe to use when `debug_assertions` are enabled,
146        /// although it still requires `unsafe` due to the behaviour in release.
147        #[must_use]
148        pub unsafe fn new() -> Unique<Self> {
149            let this = Self {
150                #[cfg(debug_assertions)]
151                checked: id::Checked::new().into_inner(),
152            };
153            // SAFETY: Ensured by callera
154            unsafe { Unique::new(this) }
155        }
156    }
157
158    // SAFETY: Ensured by caller in `DebugChecked::new`
159    unsafe impl Id for DebugChecked {}
160}
161#[cfg(target_has_atomic = "64")]
162pub use debug_checked::DebugChecked;
163
164mod lifetime {
165    use super::Id;
166    use super::Unique;
167    use core::marker::PhantomData;
168
169    /// A fully statically checked ID based on invariant lifetimes and HRTBs.
170    ///
171    /// This is the same technique as used by `GhostCell`. While theoretically the best option
172    /// (being both safe and zero-cost), its infectious nature makes it not very useful in practice.
173    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
174    pub struct Lifetime<'id> {
175        invariant: PhantomData<fn(&'id ()) -> &'id ()>,
176    }
177
178    impl Lifetime<'_> {
179        /// Create a new lifetime-based ID usable in a specific scope.
180        pub fn scope<O, F>(f: F) -> O
181        where
182            F: for<'id> FnOnce(Unique<Lifetime<'id>>) -> O,
183        {
184            f(unsafe {
185                Unique::new(Lifetime {
186                    invariant: PhantomData,
187                })
188            })
189        }
190    }
191
192    unsafe impl Id for Lifetime<'_> {}
193}
194pub use lifetime::Lifetime;