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}