uuid7/generator.rs
1//! UUIDv7 generator and related types.
2
3use crate::Uuid;
4
5pub mod with_rand08;
6
7/// A trait that defines the minimum random number generator interface for [`V7Generator`].
8pub trait Rng {
9 /// Returns the next random `u32`.
10 fn next_u32(&mut self) -> u32;
11
12 /// Returns the next random `u64`.
13 fn next_u64(&mut self) -> u64;
14}
15
16/// Represents a UUIDv7 generator that encapsulates a counter and guarantees the monotonic order of
17/// UUIDs generated within the same millisecond.
18///
19/// This type provides the interface to customize the random number generator, system clock, and
20/// clock rollback handling of a UUIDv7 generator. It also helps control the scope of guaranteed
21/// order of the generated UUIDs. The following example guarantees the process-wide (cross-thread)
22/// monotonicity using Rust's standard synchronization mechanism.
23///
24/// # Examples
25///
26/// ```rust
27/// use rand::rngs::OsRng;
28/// use std::{sync, thread};
29/// use uuid7::V7Generator;
30///
31/// let g = sync::Arc::new(sync::Mutex::new(V7Generator::with_rand08(OsRng)));
32/// thread::scope(|s| {
33/// for i in 0..4 {
34/// let g = sync::Arc::clone(&g);
35/// s.spawn(move || {
36/// for _ in 0..8 {
37/// println!("{} by thread {}", g.lock().unwrap().generate(), i);
38/// thread::yield_now();
39/// }
40/// });
41/// }
42/// });
43/// ```
44///
45/// # Generator functions
46///
47/// The generator comes with four different methods that generate a UUIDv7:
48///
49/// | Flavor | Timestamp | On big clock rewind |
50/// | -------------------------- | --------- | ------------------- |
51/// | [`generate`] | Now | Resets generator |
52/// | [`generate_or_abort`] | Now | Returns `None` |
53/// | [`generate_or_reset_core`] | Argument | Resets generator |
54/// | [`generate_or_abort_core`] | Argument | Returns `None` |
55///
56/// All of the four return a monotonically increasing UUID by reusing the previous timestamp even
57/// if the one provided is smaller than the immediately preceding UUID's. However, when such a
58/// clock rollback is considered significant (by default, more than ten seconds):
59///
60/// 1. `generate` (or_reset) methods reset the generator and return a new UUID based on the given
61/// timestamp, breaking the increasing order of UUIDs.
62/// 2. `or_abort` variants abort and return `None` immediately.
63///
64/// The `core` functions offer low-level primitives to customize the behavior.
65///
66/// [`generate`]: V7Generator::generate
67/// [`generate_or_abort`]: V7Generator::generate_or_abort
68/// [`generate_or_reset_core`]: V7Generator::generate_or_reset_core
69/// [`generate_or_abort_core`]: V7Generator::generate_or_abort_core
70#[derive(Clone, Eq, PartialEq, Debug, Default)]
71pub struct V7Generator<R> {
72 timestamp: u64,
73 counter: u64,
74
75 /// The random number generator used by the generator.
76 rng: R,
77}
78
79impl<R: Rng> V7Generator<R> {
80 /// Creates a generator instance.
81 ///
82 /// Use [`V7Generator::with_rand08()`] to create a generator with the random number generators
83 /// from `rand` crate. Although this constructor accepts [`rand::RngCore`] types for historical
84 /// reasons, such behavior is deprecated and will be removed in the future.
85 pub const fn new(rng: R) -> Self {
86 Self {
87 timestamp: 0,
88 counter: 0,
89 rng,
90 }
91 }
92
93 /// Generates a new UUIDv7 object from the current timestamp, or resets the generator upon
94 /// significant timestamp rollback.
95 ///
96 /// See the [`V7Generator`] type documentation for the description.
97 #[cfg(feature = "std")]
98 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
99 pub fn generate(&mut self) -> Uuid {
100 use std::time;
101 self.generate_or_reset_core(
102 time::SystemTime::now()
103 .duration_since(time::UNIX_EPOCH)
104 .expect("clock may have gone backwards")
105 .as_millis() as u64,
106 10_000,
107 )
108 }
109
110 /// Generates a new UUIDv7 object from the current timestamp, or returns `None` upon
111 /// significant timestamp rollback.
112 ///
113 /// See the [`V7Generator`] type documentation for the description.
114 #[cfg(feature = "std")]
115 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
116 pub fn generate_or_abort(&mut self) -> Option<Uuid> {
117 use std::time;
118 self.generate_or_abort_core(
119 time::SystemTime::now()
120 .duration_since(time::UNIX_EPOCH)
121 .expect("clock may have gone backwards")
122 .as_millis() as u64,
123 10_000,
124 )
125 }
126
127 /// Generates a new UUIDv7 object from the `unix_ts_ms` passed, or resets the generator upon
128 /// significant timestamp rollback.
129 ///
130 /// See the [`V7Generator`] type documentation for the description.
131 ///
132 /// The `rollback_allowance` parameter specifies the amount of `unix_ts_ms` rollback that is
133 /// considered significant. A suggested value is `10_000` (milliseconds).
134 ///
135 /// # Panics
136 ///
137 /// Panics if `unix_ts_ms` is not a 48-bit positive integer.
138 pub fn generate_or_reset_core(&mut self, unix_ts_ms: u64, rollback_allowance: u64) -> Uuid {
139 if let Some(value) = self.generate_or_abort_core(unix_ts_ms, rollback_allowance) {
140 value
141 } else {
142 // reset state and resume
143 self.timestamp = 0;
144 self.generate_or_abort_core(unix_ts_ms, rollback_allowance)
145 .unwrap()
146 }
147 }
148
149 /// Generates a new UUIDv7 object from the `unix_ts_ms` passed, or returns `None` upon
150 /// significant timestamp rollback.
151 ///
152 /// See the [`V7Generator`] type documentation for the description.
153 ///
154 /// The `rollback_allowance` parameter specifies the amount of `unix_ts_ms` rollback that is
155 /// considered significant. A suggested value is `10_000` (milliseconds).
156 ///
157 /// # Panics
158 ///
159 /// Panics if `unix_ts_ms` is not a 48-bit positive integer.
160 pub fn generate_or_abort_core(
161 &mut self,
162 unix_ts_ms: u64,
163 rollback_allowance: u64,
164 ) -> Option<Uuid> {
165 const MAX_COUNTER: u64 = (1 << 42) - 1;
166
167 assert!(
168 0 < unix_ts_ms && unix_ts_ms < 1 << 48,
169 "`unix_ts_ms` must be a 48-bit positive integer"
170 );
171 assert!(
172 rollback_allowance < 1 << 48,
173 "`rollback_allowance` out of reasonable range"
174 );
175
176 if unix_ts_ms > self.timestamp {
177 self.timestamp = unix_ts_ms;
178 self.counter = self.rng.next_u64() & MAX_COUNTER;
179 } else if unix_ts_ms + rollback_allowance >= self.timestamp {
180 // go on with previous timestamp if new one is not much smaller
181 self.counter += 1;
182 if self.counter > MAX_COUNTER {
183 // increment timestamp at counter overflow
184 self.timestamp += 1;
185 self.counter = self.rng.next_u64() & MAX_COUNTER;
186 }
187 } else {
188 // abort if clock went backwards to unbearable extent
189 return None;
190 }
191
192 Some(Uuid::from_fields_v7(
193 self.timestamp,
194 (self.counter >> 30) as u16,
195 ((self.counter & 0x3fff_ffff) << 32) | self.rng.next_u32() as u64,
196 ))
197 }
198
199 /// Generates a new UUIDv4 object utilizing the random number generator inside.
200 #[cfg(feature = "global_gen")]
201 pub(crate) fn generate_v4(&mut self) -> Uuid {
202 let mut bytes = [0u8; 16];
203 bytes[..8].copy_from_slice(&self.rng.next_u64().to_le_bytes());
204 bytes[8..].copy_from_slice(&self.rng.next_u64().to_le_bytes());
205 bytes[6] = 0x40 | (bytes[6] >> 4);
206 bytes[8] = 0x80 | (bytes[8] >> 2);
207 Uuid::from(bytes)
208 }
209}
210
211/// Supports operations as an infinite iterator that produces a new UUIDv7 object for each call of
212/// `next()`.
213///
214/// # Examples
215///
216/// ```rust
217/// use uuid7::V7Generator;
218///
219/// V7Generator::with_rand08(rand::thread_rng())
220/// .enumerate()
221/// .skip(4)
222/// .take(4)
223/// .for_each(|(i, e)| println!("[{}] {}", i, e));
224/// ```
225#[cfg(feature = "std")]
226#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
227impl<R: Rng> Iterator for V7Generator<R> {
228 type Item = Uuid;
229
230 fn next(&mut self) -> Option<Self::Item> {
231 Some(self.generate())
232 }
233
234 fn size_hint(&self) -> (usize, Option<usize>) {
235 (usize::MAX, None)
236 }
237}
238
239#[cfg(feature = "std")]
240#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
241impl<R: Rng> std::iter::FusedIterator for V7Generator<R> {}
242
243#[cfg(feature = "std")]
244#[cfg(test)]
245mod tests_generate_or_reset {
246 use super::{with_rand08, V7Generator};
247
248 type ThreadGen = V7Generator<with_rand08::Adapter<rand::rngs::ThreadRng>>;
249
250 /// Generates increasing UUIDs even with decreasing or constant timestamp
251 #[test]
252 fn generates_increasing_uuids_even_with_decreasing_or_constant_timestamp() {
253 let ts = 0x0123_4567_89abu64;
254 let mut g: ThreadGen = Default::default();
255 let mut prev = g.generate_or_reset_core(ts, 10_000);
256 assert_eq!(prev.as_bytes()[..6], ts.to_be_bytes()[2..]);
257 for i in 0..100_000u64 {
258 let curr = g.generate_or_reset_core(ts - i.min(4_000), 10_000);
259 assert!(prev < curr);
260 prev = curr;
261 }
262 assert!(prev.as_bytes()[..6] >= ts.to_be_bytes()[2..]);
263 }
264
265 /// Breaks increasing order of UUIDs if timestamp goes backwards a lot
266 #[test]
267 fn breaks_increasing_order_of_uuids_if_timestamp_goes_backwards_a_lot() {
268 let ts = 0x0123_4567_89abu64;
269 let mut g: ThreadGen = Default::default();
270 let mut prev = g.generate_or_reset_core(ts, 10_000);
271 assert_eq!(prev.as_bytes()[..6], ts.to_be_bytes()[2..]);
272
273 let mut curr = g.generate_or_reset_core(ts - 10_000, 10_000);
274 assert!(prev < curr);
275
276 prev = curr;
277 curr = g.generate_or_reset_core(ts - 10_001, 10_000);
278 assert!(prev > curr);
279 assert_eq!(curr.as_bytes()[..6], (ts - 10_001).to_be_bytes()[2..]);
280
281 prev = curr;
282 curr = g.generate_or_reset_core(ts - 10_002, 10_000);
283 assert!(prev < curr);
284 }
285}
286
287#[cfg(feature = "std")]
288#[cfg(test)]
289mod tests_generate_or_abort {
290 use super::{with_rand08, V7Generator};
291
292 type ThreadGen = V7Generator<with_rand08::Adapter<rand::rngs::ThreadRng>>;
293
294 /// Generates increasing UUIDs even with decreasing or constant timestamp
295 #[test]
296 fn generates_increasing_uuids_even_with_decreasing_or_constant_timestamp() {
297 let ts = 0x0123_4567_89abu64;
298 let mut g: ThreadGen = Default::default();
299 let mut prev = g.generate_or_abort_core(ts, 10_000).unwrap();
300 assert_eq!(prev.as_bytes()[..6], ts.to_be_bytes()[2..]);
301 for i in 0..100_000u64 {
302 let curr = g.generate_or_abort_core(ts - i.min(4_000), 10_000).unwrap();
303 assert!(prev < curr);
304 prev = curr;
305 }
306 assert!(prev.as_bytes()[..6] >= ts.to_be_bytes()[2..]);
307 }
308
309 /// Returns None if timestamp goes backwards a lot
310 #[test]
311 fn returns_none_if_timestamp_goes_backwards_a_lot() {
312 let ts = 0x0123_4567_89abu64;
313 let mut g: ThreadGen = Default::default();
314 let prev = g.generate_or_abort_core(ts, 10_000).unwrap();
315 assert_eq!(prev.as_bytes()[..6], ts.to_be_bytes()[2..]);
316
317 let mut curr = g.generate_or_abort_core(ts - 10_000, 10_000);
318 assert!(prev < curr.unwrap());
319
320 curr = g.generate_or_abort_core(ts - 10_001, 10_000);
321 assert!(curr.is_none());
322
323 curr = g.generate_or_abort_core(ts - 10_002, 10_000);
324 assert!(curr.is_none());
325 }
326}