Skip to main content

fast_uuid_v7/
sequential.rs

1//  fast-uuid-v7
2//  © Copyright 2026, by Marco Mengelkoch
3//  Licensed under MIT License, see License file for more details
4//  git clone https://github.com/marcomq/fast-uuid-v7
5
6use rand::rngs::SmallRng;
7use rand::{Rng, SeedableRng};
8
9use crate::{clock, format_uuid, uuid_v7_from_parts, UuidString, COUNTER_MAX, COUNTER_SEED_MASK};
10
11/// Generates strictly increasing UUID v7 values from its own, owned state.
12///
13/// Unlike the free `gen_id_*` functions, this generator does not touch the
14/// thread-local fast path, so using it has no effect on general UUID v7
15/// generation. Every `next_id` call on the same instance returns a value
16/// strictly greater than the previous one, in both numeric and lexicographic
17/// (formatted string) order.
18///
19/// This makes it suitable for assigning ids to rows read sequentially from a
20/// CSV / JSONL file: the ids preserve the input order when the resulting
21/// objects are sorted by key, e.g. in S3.
22///
23/// As prescribed by RFC 9562 §6.2, the 18-bit counter is randomly seeded at
24/// each new millisecond; seeding only its low 12 bits leaves at least 258,049
25/// ids per millisecond. Beyond that the timestamp is advanced by 1ms to keep
26/// the ordering guarantee. 56 bits stay random and the per-millisecond seed
27/// makes concurrent instances diverge, so ids from different generator
28/// instances remain collision-safe.
29///
30/// A tight generation loop can outrun that per-millisecond budget on modern
31/// hardware — `next_id` costs roughly 3ns, i.e. ~320k ids/ms — which pushes the
32/// timestamp ahead of the wall clock. It catches up again as soon as generation
33/// pauses, and any real per-row work keeps the rate well below the budget.
34///
35/// The guarantee is per instance — it is not shared across instances, threads
36/// or processes.
37///
38/// This is not random enough for cryptography!
39///
40/// # Example
41/// ```
42/// use fast_uuid_v7::SequentialGenerator;
43///
44/// let mut gen = SequentialGenerator::new();
45/// let mut previous = 0u128;
46/// for _row in 0..1000 {
47///     let id = gen.next_id();
48///     assert!(id > previous);
49///     previous = id;
50/// }
51/// ```
52///
53/// The generator is also an infinite [`Iterator`], which reads well when ids
54/// are zipped onto rows:
55/// ```
56/// use fast_uuid_v7::SequentialGenerator;
57///
58/// let rows = ["first", "second", "third"];
59/// let mut gen = SequentialGenerator::new();
60/// let ided: Vec<(u128, &str)> = gen.by_ref().zip(rows).collect();
61/// assert!(ided[0].0 < ided[1].0);
62/// ```
63pub struct SequentialGenerator {
64    rng: SmallRng,
65    clock: clock::Clock,
66    last_ms: u64,
67    counter: u32,
68}
69
70impl SequentialGenerator {
71    #[must_use]
72    pub fn new() -> Self {
73        Self {
74            rng: SmallRng::from_rng(&mut rand::rng()),
75            clock: clock::Clock::new(),
76            last_ms: 0,
77            counter: 0,
78        }
79    }
80
81    /// RFC 9562 §6.2 seeds the counter at each new timestamp tick. Randomizing
82    /// only the low 12 bits keeps almost all per-millisecond headroom while
83    /// making concurrent instances diverge instead of running in lockstep.
84    #[inline(always)]
85    fn seed_counter(&mut self) -> u32 {
86        self.rng.next_u32() & COUNTER_SEED_MASK
87    }
88
89    /// Returns the next id, strictly greater than the previously returned one.
90    #[inline]
91    #[must_use]
92    pub fn next_id(&mut self) -> u128 {
93        let advanced = if self.last_ms == 0 || self.clock.should_refresh() {
94            let sample = self.clock.refresh_timestamp();
95            // A backwards-running clock is ignored on purpose: `last_ms` must
96            // never decrease, otherwise the ordering guarantee would break.
97            if sample.ms > self.last_ms {
98                self.last_ms = sample.ms;
99                self.counter = self.seed_counter();
100                true
101            } else {
102                false
103            }
104        } else {
105            false
106        };
107
108        if !advanced {
109            if self.counter >= COUNTER_MAX {
110                self.last_ms += 1;
111                self.counter = self.seed_counter();
112            } else {
113                self.counter += 1;
114            }
115            // The timestamp only ever grows, and `uuid_v7_from_parts` shifts it
116            // by 80 bits, so anything wider than 48 bits would silently
117            // truncate and break the ordering guarantee.
118            debug_assert!(self.last_ms < (1 << 48), "timestamp exceeded 48 bits");
119        }
120
121        // 18 bit counter: 12 in rand_a, 6 in the high bits of rand_b, so that
122        // the counter stays more significant than the random part.
123        let rand_a = ((self.counter >> 6) & 0x0FFF) as u16;
124        let rand_b_high = (self.counter & 0x3F) as u64;
125        let rand_b_low = self.rng.next_u64() & 0x00FF_FFFF_FFFF_FFFF;
126
127        uuid_v7_from_parts(self.last_ms, rand_a, (rand_b_high << 56) | rand_b_low)
128    }
129
130    /// Same as [`SequentialGenerator::next_id`], formatted as a UUID string.
131    #[inline]
132    #[must_use]
133    pub fn next_id_str(&mut self) -> UuidString {
134        format_uuid(self.next_id())
135    }
136
137    /// Same as [`SequentialGenerator::next_id_str`], but heap-allocated.
138    #[inline]
139    #[must_use]
140    pub fn next_id_string(&mut self) -> String {
141        self.next_id_str().to_string()
142    }
143}
144
145impl Default for SequentialGenerator {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151/// Yields ids forever; the sequence is the same one [`SequentialGenerator::next_id`]
152/// produces, so it never returns `None`.
153impl Iterator for SequentialGenerator {
154    type Item = u128;
155
156    #[inline]
157    fn next(&mut self) -> Option<u128> {
158        Some(self.next_id())
159    }
160
161    #[inline]
162    fn size_hint(&self) -> (usize, Option<usize>) {
163        (usize::MAX, None)
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn ids_are_strictly_increasing() {
173        let mut gen = SequentialGenerator::new();
174        let mut previous = 0u128;
175        for _ in 0..1_000_000 {
176            let id = gen.next_id();
177            assert!(id > previous);
178            previous = id;
179        }
180    }
181
182    #[test]
183    fn strings_sort_like_the_ids() {
184        let mut gen = SequentialGenerator::new();
185        let ids: Vec<_> = (0..10_000).map(|_| gen.next_id_str()).collect();
186        let mut sorted = ids.clone();
187        sorted.sort();
188        assert_eq!(ids, sorted);
189    }
190
191    #[test]
192    fn counter_exhaustion_advances_timestamp() {
193        let mut gen = SequentialGenerator::new();
194        let first = gen.next_id();
195        // Far-future timestamp so a clock refresh cannot advance it and mask
196        // the rollover path we want to exercise.
197        gen.last_ms = 0xF000_0000_0000;
198        gen.counter = COUNTER_MAX;
199        let ms_before = gen.last_ms;
200        let second = gen.next_id();
201        assert_eq!(gen.last_ms, ms_before + 1);
202        assert!(gen.counter <= COUNTER_SEED_MASK);
203        assert!(second > first);
204    }
205
206    #[test]
207    fn counter_is_reseeded_on_a_new_millisecond() {
208        let mut gen = SequentialGenerator::new();
209        let first = gen.next_id();
210        let ms_before = gen.last_ms;
211        // Push the counter beyond any value a reseed could produce, so the
212        // assertion below can only pass if the new millisecond reseeded it.
213        gen.counter = COUNTER_SEED_MASK + 1000;
214        std::thread::sleep(std::time::Duration::from_millis(2));
215        let second = gen.next_id();
216        assert!(gen.last_ms > ms_before);
217        assert!(gen.counter <= COUNTER_SEED_MASK);
218        assert!(second > first);
219    }
220
221    #[test]
222    fn instances_do_not_share_one_counter_sequence() {
223        let counters: Vec<u32> = (0..8)
224            .map(|_| {
225                let mut gen = SequentialGenerator::new();
226                let _ = gen.next_id();
227                gen.counter
228            })
229            .collect();
230        assert!(counters.iter().all(|c| *c <= COUNTER_SEED_MASK));
231        // Seeds are 12 bit, so eight identical ones has probability 4096^-7.
232        assert!(counters.iter().any(|c| *c != counters[0]));
233    }
234
235    #[test]
236    fn version_and_variant_survive_counter_rollover() {
237        let mut gen = SequentialGenerator::new();
238        // Far-future timestamp so clock refreshes cannot advance it and mask
239        // the rollover path; re-exhausting the counter forces it every round.
240        gen.last_ms = 0xF000_0000_0000;
241        let mut previous = 0u128;
242        for _ in 0..1000 {
243            gen.counter = COUNTER_MAX;
244            let id = gen.next_id();
245            assert_eq!((id >> 76) & 0xF, 7);
246            assert_eq!((id >> 62) & 0x3, 0b10);
247            assert!(id > previous);
248            previous = id;
249        }
250    }
251
252    #[test]
253    fn iterator_yields_the_same_increasing_sequence() {
254        let mut gen = SequentialGenerator::new();
255        let ids: Vec<u128> = gen.by_ref().take(10_000).collect();
256        assert_eq!(ids.len(), 10_000);
257        assert!(ids.windows(2).all(|pair| pair[0] < pair[1]));
258        assert!(gen.next_id() > ids[ids.len() - 1]);
259    }
260
261    #[test]
262    fn next_id_string_matches_next_id_str() {
263        let mut gen = SequentialGenerator::new();
264        let owned = gen.next_id_string();
265        assert_eq!(owned.len(), 36);
266        assert!(owned.as_str() < gen.next_id_str().as_str());
267    }
268
269    #[test]
270    fn backwards_clock_does_not_break_ordering() {
271        let mut gen = SequentialGenerator::new();
272        let first = gen.next_id();
273        // Simulate a clock that jumped forward once and then back: every later
274        // wall-clock sample is now in the past relative to `last_ms`.
275        gen.last_ms += 60_000;
276        let mut previous = gen.next_id();
277        assert!(previous > first);
278        for _ in 0..10_000 {
279            let id = gen.next_id();
280            assert!(id > previous);
281            previous = id;
282        }
283    }
284
285    #[test]
286    fn timestamp_matches_wall_clock() {
287        let now_ms = std::time::SystemTime::now()
288            .duration_since(std::time::UNIX_EPOCH)
289            .unwrap()
290            .as_millis() as u64;
291        let id = SequentialGenerator::new().next_id();
292        let timestamp = (id >> 80) as u64;
293        assert!(timestamp.abs_diff(now_ms) < 1000);
294    }
295
296    #[test]
297    fn instances_are_independent() {
298        let (mut first, mut second) = (SequentialGenerator::new(), SequentialGenerator::new());
299        assert_ne!(first.next_id(), second.next_id());
300    }
301
302    #[test]
303    fn string_and_u128_orderings_agree() {
304        let mut gen = SequentialGenerator::new();
305        let ids: Vec<u128> = (0..10_000).map(|_| gen.next_id()).collect();
306        for pair in ids.windows(2) {
307            assert!(format_uuid(pair[0]).as_str() < format_uuid(pair[1]).as_str());
308        }
309    }
310
311    #[test]
312    fn version_and_variant_are_preserved() {
313        let mut gen = SequentialGenerator::new();
314        for _ in 0..1000 {
315            let id = gen.next_id();
316            assert_eq!((id >> 76) & 0xF, 7);
317            assert_eq!((id >> 62) & 0x3, 0b10);
318        }
319    }
320}