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;