Skip to main content

fast_uuid_v7/
lib.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};
8use std::cell::UnsafeCell;
9#[cfg(all(
10    any(target_arch = "x86", target_arch = "x86_64"),
11    not(target_feature = "ssse3")
12))]
13use std::sync::atomic::{AtomicU8, Ordering};
14
15const COUNTER_MAX: u32 = 0x3FFFF;
16const COUNTER_SEED_MASK: u32 = 0x0FFF;
17const HEX: &[u8; 16] = b"0123456789abcdef";
18#[cfg(not(target_arch = "aarch64"))]
19const HEX_PAIRS: [[u8; 2]; 256] = hex_pairs();
20#[cfg(all(
21    any(target_arch = "x86", target_arch = "x86_64"),
22    not(target_feature = "ssse3")
23))]
24static X86_FORMATTER: AtomicU8 = AtomicU8::new(0);
25
26#[cfg(not(target_arch = "aarch64"))]
27const fn hex_pairs() -> [[u8; 2]; 256] {
28    let mut pairs = [[0u8; 2]; 256];
29    let mut i = 0;
30
31    while i < 256 {
32        pairs[i][0] = HEX[i >> 4];
33        pairs[i][1] = HEX[i & 0x0f];
34        i += 1;
35    }
36
37    pairs
38}
39
40#[cfg(all(
41    any(target_arch = "x86", target_arch = "x86_64"),
42    not(target_feature = "ssse3")
43))]
44#[inline(always)]
45fn x86_has_ssse3() -> bool {
46    match X86_FORMATTER.load(Ordering::Relaxed) {
47        2 => true,
48        1 => false,
49        _ => {
50            let has_ssse3 = std::arch::is_x86_feature_detected!("ssse3");
51            X86_FORMATTER.store(if has_ssse3 { 2 } else { 1 }, Ordering::Relaxed);
52            has_ssse3
53        }
54    }
55}
56
57mod clock;
58mod sequential;
59
60pub use sequential::SequentialGenerator;
61
62struct ThreadState {
63    rng: SmallRng,
64    last_ms: u64,
65    last_nanos_within_ms: u32,
66    last_sampled_nanos_within_ms: u32,
67    counter: u32,
68    clock: clock::Clock,
69}
70
71impl ThreadState {
72    fn new() -> Self {
73        Self {
74            rng: SmallRng::from_rng(&mut rand::rng()),
75            last_ms: 0,
76            last_nanos_within_ms: 0,
77            last_sampled_nanos_within_ms: 0,
78            counter: 0,
79            clock: clock::Clock::new(),
80        }
81    }
82
83    #[inline(always)]
84    fn seed_counter(&mut self) -> u32 {
85        // RFC 9562 allows seeding only a portion of a fixed-length counter.
86        // We randomize the low 12 bits to keep the full 18-bit layout while
87        // preserving almost all per-millisecond headroom before rollover.
88        self.rng.next_u32() & COUNTER_SEED_MASK
89    }
90
91    #[inline(always)]
92    fn refresh_time(&mut self) -> bool {
93        let sample = self.clock.refresh_timestamp();
94        self.record_time_sample(sample, true)
95    }
96
97    #[inline(always)]
98    fn current_timestamp_sample(&self) -> clock::TimestampSample {
99        clock::TimestampSample {
100            ms: self.last_ms,
101            nanos_within_ms: self.last_nanos_within_ms,
102        }
103    }
104
105    #[inline(always)]
106    fn get_time(&mut self) -> u64 {
107        if self.last_ms == 0 || self.clock.should_refresh() {
108            self.refresh_time();
109        }
110        self.last_ms
111    }
112
113    #[inline(always)]
114    fn get_time_and_counter(&mut self) -> (u64, u32) {
115        if (self.last_ms == 0 || self.clock.should_refresh()) && self.refresh_time() {
116            (self.last_ms, self.counter)
117        } else {
118            let c = self.counter;
119            let mut current_timestamp = self.last_ms;
120
121            // If counter is exhausted (18 bits = 262,143), increment timestamp to preserve monotonicity
122            if c >= COUNTER_MAX {
123                current_timestamp += 1;
124                self.last_ms = current_timestamp;
125                self.last_nanos_within_ms = 0;
126                self.last_sampled_nanos_within_ms = 0;
127                self.counter = self.seed_counter();
128                (current_timestamp, self.counter)
129            } else {
130                let inc = c.wrapping_add(1);
131                self.counter = inc;
132                (current_timestamp, inc)
133            }
134        }
135    }
136
137    #[inline(always)]
138    fn record_time_sample(&mut self, sample: clock::TimestampSample, refreshed: bool) -> bool {
139        if sample.ms > self.last_ms {
140            if refreshed {
141                self.last_sampled_nanos_within_ms = sample.nanos_within_ms;
142            }
143            self.last_ms = sample.ms;
144            self.last_nanos_within_ms = sample.nanos_within_ms;
145            self.counter = self.seed_counter();
146            true
147        } else if sample.ms == self.last_ms {
148            if refreshed {
149                self.last_sampled_nanos_within_ms = sample.nanos_within_ms;
150            }
151            self.last_nanos_within_ms = self.last_nanos_within_ms.max(sample.nanos_within_ms);
152            false
153        } else {
154            false
155        }
156    }
157
158    #[inline(always)]
159    fn sample_time(&mut self) -> clock::TimestampSample {
160        let refreshed = self.last_ms == 0 || self.clock.should_refresh();
161        let sample = if refreshed {
162            self.clock.refresh_timestamp()
163        } else {
164            self.clock
165                .estimated_timestamp(self.last_ms, self.last_sampled_nanos_within_ms)
166        };
167        self.record_time_sample(sample, refreshed);
168        self.current_timestamp_sample()
169    }
170}
171
172thread_local! {
173    static STATE: UnsafeCell<ThreadState> = UnsafeCell::new(ThreadState::new());
174}
175
176/// Runs `f` with exclusive access to the thread-local [`ThreadState`].
177///
178/// # Safety invariant
179/// This hands out a `&mut ThreadState` from an `UnsafeCell` without a runtime
180/// borrow flag. That is sound only because the state is thread-local (no other
181/// thread can reach it) and `f` never re-enters `with_state` — none of the ID
182/// generators call back into the thread-local while holding the reference, so
183/// the `&mut` is never aliased.
184#[inline(always)]
185fn with_state<R>(f: impl FnOnce(&mut ThreadState) -> R) -> R {
186    STATE.with(|state_cell| {
187        // SAFETY: see the invariant above; access is single-threaded and
188        // non-reentrant, so this is the only live reference to the state.
189        let state = unsafe { &mut *state_cell.get() };
190        f(state)
191    })
192}
193
194#[inline]
195fn compose_rand_a(random: u16, fraction: u16, bits: u8) -> u16 {
196    debug_assert!(bits <= 12);
197
198    let random_bits = 12 - bits;
199    let random_mask = if random_bits == 0 {
200        0
201    } else {
202        (1u16 << random_bits) - 1
203    };
204
205    ((fraction & ((1u16 << bits) - 1)) << random_bits) | (random & random_mask)
206}
207
208#[inline]
209fn uuid_v7_from_parts(timestamp_ms: u64, rand_a: u16, rand_b: u64) -> u128 {
210    let timestamp_part = (timestamp_ms as u128) << 80;
211    let version_part = 7u128 << 76;
212    let variant_part = 2u128 << 62;
213
214    timestamp_part
215        | version_part
216        | ((rand_a as u128) << 64)
217        | variant_part
218        | ((rand_b & 0x3FFF_FFFF_FFFF_FFFF) as u128)
219}
220
221/// Generates a unique identifier compatible with UUID v7.
222///
223/// The identifier is a `u128` value composed of:
224/// - 48 bits: Current timestamp in milliseconds.
225/// -  4 bits: Version (7).
226/// - 12 bits: Random data.
227/// -  2 bits: Variant (10..).
228/// - 62 bits: Random data.
229///
230/// **Randomness:**
231/// This function uses 74 bits of randomness. This provides extremely low
232/// collision probability across distributed systems but does not guarantee monotonicity
233/// for IDs generated within the same millisecond on the same thread.
234///
235/// fast-uuid-v7 is is not random enough for cryptography!
236#[inline]
237pub fn gen_id_u128() -> u128 {
238    with_state(|state| {
239        let timestamp = state.get_time();
240
241        // We need 74 bits of randomness. SmallRng generates 64 bits per call.
242        let r1 = state.rng.next_u32();
243        let r2 = state.rng.next_u64();
244
245        // rand_a: 12 bits (from r1)
246        let rand_a = (r1 & 0x0FFF) as u16;
247        // rand_b: 62 bits (from r2)
248        let rand_b = r2 & 0x3FFF_FFFF_FFFF_FFFF;
249
250        uuid_v7_from_parts(timestamp, rand_a, rand_b)
251    })
252}
253
254/// Alias for `gen_id_u128`.
255#[inline]
256pub fn gen_id() -> u128 {
257    gen_id_u128()
258}
259
260/// Generates a UUID v7 with an RFC 9562-style sub-millisecond time fraction.
261///
262/// The 48-bit UUID timestamp remains milliseconds since the Unix epoch. This
263/// API fills the high `bits` of `rand_a` with a scaled fraction of the current
264/// millisecond and leaves the remaining random bits unchanged. On supported
265/// counter backends, the millisecond timestamp comes from wall-clock time, but
266/// the sub-millisecond fraction is often estimated between wall-clock refreshes
267/// instead of being freshly measured on every call.
268///
269/// This can improve sort locality for IDs produced within the same millisecond,
270/// but it does not provide true nanosecond ordering or distributed monotonicity.
271#[inline]
272fn gen_id_with_sub_ms_bits(bits: u8) -> u128 {
273    debug_assert!(matches!(bits, 4 | 8 | 12));
274
275    with_state(|state| {
276        let sample = state.sample_time();
277
278        let r1 = state.rng.next_u32();
279        let r2 = state.rng.next_u64();
280
281        let fraction = sample.sub_ms_fraction(bits);
282        let rand_a = compose_rand_a((r1 & 0x0FFF) as u16, fraction, bits);
283        let rand_b = r2 & 0x3FFF_FFFF_FFFF_FFFF;
284
285        uuid_v7_from_parts(sample.ms, rand_a, rand_b)
286    })
287}
288
289/// Generates a UUID v7 with a 4-bit sub-millisecond fraction in `rand_a`.
290#[inline]
291pub fn gen_id_with_sub_ms_4() -> u128 {
292    gen_id_with_sub_ms_bits(4)
293}
294
295/// Generates a UUID v7 with an 8-bit sub-millisecond fraction in `rand_a`.
296#[inline]
297pub fn gen_id_with_sub_ms_8() -> u128 {
298    gen_id_with_sub_ms_bits(8)
299}
300
301/// Generates a UUID v7 with a 12-bit sub-millisecond fraction in `rand_a`.
302#[inline]
303pub fn gen_id_with_sub_ms_12() -> u128 {
304    gen_id_with_sub_ms_bits(12)
305}
306
307/// Generates a UUID v7 string using the `gen_id_u128` function.
308///
309/// The returned string is in the format `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
310///
311/// **Note on Sorting:**
312/// Since the counter is thread-local and resets every millisecond, IDs generated
313/// concurrently by multiple threads within the same millisecond are not guaranteed
314/// to be globally monotonic.
315///
316/// This is not random enough for cryptography!
317pub fn gen_id_string() -> String {
318    gen_id_str().to_string()
319}
320
321/// Generates a UUID v7 string on the stack, avoiding heap allocation.
322///
323/// The returned [`UuidString`] owns its bytes. Borrow it when passing it to APIs
324/// that need `&str`, or call [`UuidString::as_str`] explicitly.
325pub fn gen_id_str() -> UuidString {
326    format_uuid(gen_id_u128())
327}
328
329/// Formats a u128 UUID into a stack-allocated string representation.
330/// `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
331pub fn format_uuid(id: u128) -> UuidString {
332    #[cfg(all(
333        any(target_arch = "x86", target_arch = "x86_64"),
334        target_feature = "ssse3"
335    ))]
336    {
337        // SAFETY: SSSE3 is enabled for this compilation unit.
338        return unsafe { format_uuid_simd(id) };
339    }
340
341    #[cfg(all(
342        any(target_arch = "x86", target_arch = "x86_64"),
343        not(target_feature = "ssse3")
344    ))]
345    {
346        if x86_has_ssse3() {
347            // SAFETY: x86_has_ssse3 verifies SSSE3 support before calling
348            // the target-feature-specialized formatter.
349            return unsafe { format_uuid_simd(id) };
350        }
351    }
352
353    #[cfg(target_arch = "aarch64")]
354    {
355        // SAFETY: NEON/AdvSIMD is part of the aarch64 baseline.
356        uuid_string_from_hex(unsafe { format_uuid_hex_neon(id) })
357    }
358
359    #[cfg(not(target_arch = "aarch64"))]
360    {
361        format_uuid_scalar(id)
362    }
363}
364
365/// Formats a u128 UUID into a stack-allocated lowercase hex representation.
366/// `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
367pub fn format_uuid_hex(id: u128) -> UuidHex {
368    UuidHex(format_uuid_hex_bytes(id))
369}
370
371fn format_uuid_hex_bytes(id: u128) -> [u8; 32] {
372    #[cfg(all(
373        any(target_arch = "x86", target_arch = "x86_64"),
374        target_feature = "ssse3"
375    ))]
376    {
377        // SAFETY: SSSE3 is enabled for this compilation unit.
378        return unsafe { format_uuid_hex_simd(id) };
379    }
380
381    #[cfg(all(
382        any(target_arch = "x86", target_arch = "x86_64"),
383        not(target_feature = "ssse3")
384    ))]
385    {
386        if x86_has_ssse3() {
387            // SAFETY: x86_has_ssse3 verifies SSSE3 support before calling
388            // the target-feature-specialized formatter.
389            return unsafe { format_uuid_hex_simd(id) };
390        }
391    }
392
393    #[cfg(target_arch = "aarch64")]
394    {
395        // SAFETY: NEON/AdvSIMD is part of the aarch64 baseline.
396        unsafe { format_uuid_hex_neon(id) }
397    }
398
399    #[cfg(not(target_arch = "aarch64"))]
400    {
401        format_uuid_hex_scalar(id)
402    }
403}
404
405#[cfg(not(target_arch = "aarch64"))]
406#[inline(always)]
407fn format_uuid_scalar(id: u128) -> UuidString {
408    let mut out = UuidString([0; 36]);
409    let bytes = id.to_be_bytes();
410
411    unsafe {
412        let ptr = out.0.as_mut_ptr();
413
414        // Group 1: 8 chars (4 bytes)
415        let pair = HEX_PAIRS[bytes[0] as usize];
416        *ptr.add(0) = pair[0];
417        *ptr.add(1) = pair[1];
418        let pair = HEX_PAIRS[bytes[1] as usize];
419        *ptr.add(2) = pair[0];
420        *ptr.add(3) = pair[1];
421        let pair = HEX_PAIRS[bytes[2] as usize];
422        *ptr.add(4) = pair[0];
423        *ptr.add(5) = pair[1];
424        let pair = HEX_PAIRS[bytes[3] as usize];
425        *ptr.add(6) = pair[0];
426        *ptr.add(7) = pair[1];
427        *ptr.add(8) = b'-';
428
429        // Group 2: 4 chars (2 bytes)
430        let pair = HEX_PAIRS[bytes[4] as usize];
431        *ptr.add(9) = pair[0];
432        *ptr.add(10) = pair[1];
433        let pair = HEX_PAIRS[bytes[5] as usize];
434        *ptr.add(11) = pair[0];
435        *ptr.add(12) = pair[1];
436        *ptr.add(13) = b'-';
437
438        // Group 3: 4 chars (2 bytes)
439        let pair = HEX_PAIRS[bytes[6] as usize];
440        *ptr.add(14) = pair[0];
441        *ptr.add(15) = pair[1];
442        let pair = HEX_PAIRS[bytes[7] as usize];
443        *ptr.add(16) = pair[0];
444        *ptr.add(17) = pair[1];
445        *ptr.add(18) = b'-';
446
447        // Group 4: 4 chars (2 bytes)
448        let pair = HEX_PAIRS[bytes[8] as usize];
449        *ptr.add(19) = pair[0];
450        *ptr.add(20) = pair[1];
451        let pair = HEX_PAIRS[bytes[9] as usize];
452        *ptr.add(21) = pair[0];
453        *ptr.add(22) = pair[1];
454        *ptr.add(23) = b'-';
455
456        // Group 5: 12 chars (6 bytes)
457        let pair = HEX_PAIRS[bytes[10] as usize];
458        *ptr.add(24) = pair[0];
459        *ptr.add(25) = pair[1];
460        let pair = HEX_PAIRS[bytes[11] as usize];
461        *ptr.add(26) = pair[0];
462        *ptr.add(27) = pair[1];
463        let pair = HEX_PAIRS[bytes[12] as usize];
464        *ptr.add(28) = pair[0];
465        *ptr.add(29) = pair[1];
466        let pair = HEX_PAIRS[bytes[13] as usize];
467        *ptr.add(30) = pair[0];
468        *ptr.add(31) = pair[1];
469        let pair = HEX_PAIRS[bytes[14] as usize];
470        *ptr.add(32) = pair[0];
471        *ptr.add(33) = pair[1];
472        let pair = HEX_PAIRS[bytes[15] as usize];
473        *ptr.add(34) = pair[0];
474        *ptr.add(35) = pair[1];
475    }
476    out
477}
478
479#[cfg(not(target_arch = "aarch64"))]
480#[inline(always)]
481fn format_uuid_hex_scalar(id: u128) -> [u8; 32] {
482    let bytes = id.to_be_bytes();
483    let mut out = [0u8; 32];
484
485    for (idx, byte) in bytes.iter().enumerate() {
486        let pair = HEX_PAIRS[*byte as usize];
487        out[idx * 2] = pair[0];
488        out[idx * 2 + 1] = pair[1];
489    }
490
491    out
492}
493
494#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
495#[target_feature(enable = "ssse3")]
496unsafe fn format_uuid_simd(id: u128) -> UuidString {
497    uuid_string_from_hex(unsafe { format_uuid_hex_simd(id) })
498}
499
500#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
501#[target_feature(enable = "ssse3")]
502unsafe fn format_uuid_hex_simd(id: u128) -> [u8; 32] {
503    #[cfg(target_arch = "x86")]
504    use std::arch::x86::{
505        __m128i, _mm_and_si128, _mm_loadu_si128, _mm_set1_epi8, _mm_shuffle_epi8, _mm_srli_epi16,
506        _mm_storeu_si128, _mm_unpackhi_epi8, _mm_unpacklo_epi8,
507    };
508    #[cfg(target_arch = "x86_64")]
509    use std::arch::x86_64::{
510        __m128i, _mm_and_si128, _mm_loadu_si128, _mm_set1_epi8, _mm_shuffle_epi8, _mm_srli_epi16,
511        _mm_storeu_si128, _mm_unpackhi_epi8, _mm_unpacklo_epi8,
512    };
513
514    let bytes = id.to_be_bytes();
515    let mut hex = [0u8; 32];
516
517    unsafe {
518        let raw = _mm_loadu_si128(bytes.as_ptr() as *const __m128i);
519        let mask = _mm_set1_epi8(0x0f);
520        let table = _mm_loadu_si128(HEX.as_ptr() as *const __m128i);
521
522        let lo = _mm_and_si128(raw, mask);
523        let hi = _mm_and_si128(_mm_srli_epi16(raw, 4), mask);
524        let nibbles_lo = _mm_unpacklo_epi8(hi, lo);
525        let nibbles_hi = _mm_unpackhi_epi8(hi, lo);
526
527        let hex_lo = _mm_shuffle_epi8(table, nibbles_lo);
528        let hex_hi = _mm_shuffle_epi8(table, nibbles_hi);
529
530        _mm_storeu_si128(hex.as_mut_ptr() as *mut __m128i, hex_lo);
531        _mm_storeu_si128(hex.as_mut_ptr().add(16) as *mut __m128i, hex_hi);
532    }
533
534    hex
535}
536
537#[cfg(target_arch = "aarch64")]
538#[inline(always)]
539unsafe fn format_uuid_hex_neon(id: u128) -> [u8; 32] {
540    use std::arch::aarch64::{
541        uint8x16_t, vandq_u8, vdupq_n_u8, vld1q_u8, vqtbl1q_u8, vshrq_n_u8, vst1q_u8, vzip1q_u8,
542        vzip2q_u8,
543    };
544
545    let bytes = id.to_be_bytes();
546    let mut hex = [0u8; 32];
547
548    unsafe {
549        let raw = vld1q_u8(bytes.as_ptr());
550        let table = vld1q_u8(HEX.as_ptr());
551        let mask = vdupq_n_u8(0x0f);
552
553        let lo = vandq_u8(raw, mask);
554        let hi = vshrq_n_u8::<4>(raw);
555        let nibbles_lo: uint8x16_t = vzip1q_u8(hi, lo);
556        let nibbles_hi: uint8x16_t = vzip2q_u8(hi, lo);
557
558        let hex_lo = vqtbl1q_u8(table, nibbles_lo);
559        let hex_hi = vqtbl1q_u8(table, nibbles_hi);
560
561        vst1q_u8(hex.as_mut_ptr(), hex_lo);
562        vst1q_u8(hex.as_mut_ptr().add(16), hex_hi);
563    }
564
565    hex
566}
567
568#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
569#[inline(always)]
570fn uuid_string_from_hex(hex: [u8; 32]) -> UuidString {
571    let mut out = UuidString([0; 36]);
572
573    out.0[0..8].copy_from_slice(&hex[0..8]);
574    out.0[8] = b'-';
575    out.0[9..13].copy_from_slice(&hex[8..12]);
576    out.0[13] = b'-';
577    out.0[14..18].copy_from_slice(&hex[12..16]);
578    out.0[18] = b'-';
579    out.0[19..23].copy_from_slice(&hex[16..20]);
580    out.0[23] = b'-';
581    out.0[24..36].copy_from_slice(&hex[20..32]);
582
583    out
584}
585
586/// A stack-allocated string representation of a UUID (36 bytes).
587///
588/// This type owns its bytes and implements `Deref<Target = str>` and
589/// `AsRef<str>`, so it can be borrowed by most APIs that need a string slice.
590/// It avoids heap allocation, making it faster than [`gen_id_string`].
591#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
592pub struct UuidString([u8; 36]);
593
594/// A stack-allocated hex representation of a UUID without dashes (32 bytes).
595#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
596pub struct UuidHex([u8; 32]);
597
598impl UuidString {
599    /// Returns this UUID as a string slice.
600    #[inline]
601    pub fn as_str(&self) -> &str {
602        // SAFETY: The buffer is always filled with valid ASCII (hex + dashes).
603        unsafe { std::str::from_utf8_unchecked(&self.0) }
604    }
605
606    /// Returns the underlying UUID string bytes.
607    #[inline]
608    pub fn as_bytes(&self) -> &[u8; 36] {
609        &self.0
610    }
611}
612
613impl std::ops::Deref for UuidString {
614    type Target = str;
615
616    #[inline]
617    fn deref(&self) -> &str {
618        self.as_str()
619    }
620}
621
622impl AsRef<str> for UuidString {
623    #[inline]
624    fn as_ref(&self) -> &str {
625        self.as_str()
626    }
627}
628
629impl PartialEq<str> for UuidString {
630    #[inline]
631    fn eq(&self, other: &str) -> bool {
632        self.as_str() == other
633    }
634}
635
636impl PartialEq<&str> for UuidString {
637    #[inline]
638    fn eq(&self, other: &&str) -> bool {
639        self.as_str() == *other
640    }
641}
642
643impl std::fmt::Display for UuidString {
644    #[inline]
645    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646        f.write_str(self.as_str())
647    }
648}
649
650impl UuidHex {
651    /// Returns this UUID as a lowercase hex string slice without dashes.
652    #[inline]
653    pub fn as_str(&self) -> &str {
654        // SAFETY: The buffer is always filled with valid ASCII hex.
655        unsafe { std::str::from_utf8_unchecked(&self.0) }
656    }
657
658    /// Returns the underlying UUID hex bytes.
659    #[inline]
660    pub fn as_bytes(&self) -> &[u8; 32] {
661        &self.0
662    }
663}
664
665impl std::ops::Deref for UuidHex {
666    type Target = str;
667
668    #[inline]
669    fn deref(&self) -> &str {
670        self.as_str()
671    }
672}
673
674impl AsRef<str> for UuidHex {
675    #[inline]
676    fn as_ref(&self) -> &str {
677        self.as_str()
678    }
679}
680
681impl std::fmt::Display for UuidHex {
682    #[inline]
683    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684        f.write_str(self.as_str())
685    }
686}
687
688/// Generates a UUID v7 with an RFC-style seeded 18-bit monotonic counter and 56 bits of randomness.
689///
690/// This guarantees per-thread monotonicity (up to ~262k IDs/ms) but has higher
691/// collision risk across different nodes if the random part is exhausted.
692#[inline]
693pub fn gen_id_with_count() -> u128 {
694    with_state(|state| {
695        let (timestamp, counter) = state.get_time_and_counter();
696
697        // Use 18 bits for counter: 12 in rand_a, 6 in rand_b high.
698        let rand_a = ((counter >> 6) & 0x0FFF) as u16;
699        let rand_b_high = counter & 0x3F;
700
701        let rand_nr = state.rng.next_u64();
702
703        let rand_b_low = rand_nr & 0x00FF_FFFF_FFFF_FFFF;
704        let random_part = ((rand_b_high as u64) << 56) | rand_b_low;
705
706        uuid_v7_from_parts(timestamp, rand_a, random_part)
707    })
708}
709
710#[inline]
711pub fn gen_id_with_count_str() -> UuidString {
712    format_uuid(gen_id_with_count())
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    #[test]
720    fn test_deadline_is_reached_at_zero_or_after_deadline() {
721        assert!(clock::deadline_reached(10, 0));
722        assert!(clock::deadline_reached(10, 10));
723        assert!(clock::deadline_reached(11, 10));
724        assert!(!clock::deadline_reached(9, 10));
725    }
726
727    #[test]
728    fn test_deadline_handles_counter_wraparound() {
729        assert!(!clock::deadline_reached(u64::MAX - 1, 3));
730        assert!(clock::deadline_reached(3, u64::MAX - 1));
731    }
732
733    #[test]
734    fn test_ticks_until_next_refresh_caps_at_half_millisecond() {
735        assert_eq!(clock::ticks_until_next_refresh(1_000, 0), 500);
736        assert_eq!(clock::ticks_until_next_refresh(1_000, 250_000), 500);
737        assert_eq!(clock::ticks_until_next_refresh(1_000, 500_000), 500);
738        assert_eq!(clock::ticks_until_next_refresh(1_000, 750_000), 250);
739        assert_eq!(clock::ticks_until_next_refresh(1_000, 999_999), 1);
740        assert_eq!(clock::ticks_until_next_refresh(1_000, 1_000_000), 500);
741    }
742
743    #[test]
744    fn test_ticks_until_next_refresh_never_returns_zero() {
745        assert_eq!(clock::ticks_until_next_refresh(0, 0), 1);
746        assert_eq!(clock::ticks_until_next_refresh(1, 999_999), 1);
747    }
748
749    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
750    #[test]
751    fn test_clock_without_counter_backend_always_refreshes() {
752        let mut clock = clock::Clock::new();
753        assert!(clock.should_refresh());
754
755        clock.record_sample(0, 0);
756        assert!(clock.should_refresh());
757    }
758
759    #[test]
760    /// test with `cargo test --release -- test_next_id_performance --nocapture`
761    fn test_next_id_performance() {
762        let start = std::time::Instant::now();
763        for _ in 0..10_000_000 {
764            let _ = gen_id_u128();
765        }
766        println!("Generated 10,000,000 IDs in {:?}", start.elapsed());
767    }
768
769    #[test]
770    fn test_next_id_uniqueness() {
771        let mut set = std::collections::HashSet::with_capacity(1_000_000);
772        for _ in 0..1_000_000 {
773            let id = gen_id_u128();
774            assert!(set.insert(id), "Duplicate ID generated: {:032x}", id);
775        }
776    }
777
778    #[test]
779    /// IDs are sorted correctly per thread.
780    /// Capacity is ~262k IDs per ms (18 bits).
781    fn test_next_id_ordering() {
782        let mut last_id = 0;
783        for _ in 0..1_000_000 {
784            let id = gen_id_with_count();
785            if last_id != 0 {
786                assert!(
787                    id > last_id,
788                    "IDs are not ordered: {:032x} <= {:032x}",
789                    id,
790                    last_id
791                );
792            }
793            last_id = id;
794        }
795    }
796
797    #[test]
798    fn test_next_id_string() {
799        let id_str = gen_id_string();
800        assert_eq!(id_str.len(), 36);
801        assert!(uuid::Uuid::parse_str(&id_str).is_ok());
802    }
803
804    #[test]
805    fn test_format_uuid_correctness() {
806        let id = gen_id_u128();
807        let formatted = format_uuid(id);
808        let uuid_crate_str = uuid::Uuid::from_u128(id).to_string();
809        assert_eq!(formatted.as_ref(), uuid_crate_str);
810    }
811
812    #[test]
813    fn test_format_uuid_hex_correctness() {
814        let id = gen_id_u128();
815        let formatted = format_uuid_hex(id);
816        let uuid_crate_str = uuid::Uuid::from_u128(id).simple().to_string();
817        assert_eq!(formatted.as_ref(), uuid_crate_str);
818    }
819
820    #[test]
821    fn test_uuid_string_str_accessors() {
822        fn accepts_str(value: &str) -> usize {
823            value.len()
824        }
825
826        let formatted = gen_id_str();
827        assert_eq!(accepts_str(&formatted), 36);
828        assert_eq!(accepts_str(formatted.as_str()), 36);
829        assert!(uuid::Uuid::parse_str(formatted.as_str()).is_ok());
830    }
831
832    #[test]
833    fn test_gen_id_structure() {
834        let id = gen_id();
835        let uuid = uuid::Uuid::from_u128(id);
836        assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
837        assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
838    }
839
840    #[test]
841    fn test_gen_id_with_count_structure() {
842        let id = gen_id_with_count();
843        let uuid = uuid::Uuid::from_u128(id);
844        assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
845        assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
846    }
847
848    #[test]
849    fn test_gen_id_with_sub_ms_4_has_uuid_v7_layout() {
850        let id = gen_id_with_sub_ms_4();
851        let uuid = uuid::Uuid::from_u128(id);
852        assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
853        assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
854
855        let rand_a = ((id >> 64) & 0x0FFF) as u16;
856        assert!(rand_a <= 0x0FFF);
857    }
858
859    #[test]
860    fn test_sub_ms_fraction_placement_for_expected_bit_widths() {
861        let sample = clock::TimestampSample {
862            ms: 0,
863            nanos_within_ms: 654_321,
864        };
865        let random = 0b1010_0110_1101u16;
866
867        for bits in [4, 8, 12] {
868            let fraction = sample.sub_ms_fraction(bits);
869            let rand_a = compose_rand_a(random, fraction, bits);
870            let random_bits = 12 - bits;
871            let extracted_fraction = rand_a >> random_bits;
872
873            assert_eq!(extracted_fraction, fraction);
874        }
875    }
876
877    #[test]
878    fn test_remaining_rand_a_bits_stay_random() {
879        let sample = clock::TimestampSample {
880            ms: 0,
881            nanos_within_ms: 789_123,
882        };
883        let random = 0b1101_0011_1010u16;
884        let bits = 8;
885
886        let fraction = sample.sub_ms_fraction(bits);
887        let rand_a = compose_rand_a(random, fraction, bits);
888
889        assert_eq!(rand_a & 0x000F, random & 0x000F);
890    }
891
892    #[test]
893    fn test_exact_millisecond_boundary_sub_ms_fraction_stays_in_last_bucket() {
894        let sample = clock::TimestampSample {
895            ms: 0,
896            nanos_within_ms: 1_000_000,
897        };
898        let rand_a = compose_rand_a(0, sample.sub_ms_fraction(12), 12);
899
900        assert_eq!(rand_a, 0x0FFF);
901    }
902
903    #[test]
904    fn test_rand_b_remains_random() {
905        let timestamp = 1_748_000_000_000u64;
906        let rand_a = 0x0ABCu16;
907        let rand_b = 0x2ABC_DEF0_1234_5678u64;
908
909        let id = uuid_v7_from_parts(timestamp, rand_a, rand_b);
910
911        assert_eq!(id >> 80, timestamp as u128);
912        assert_eq!((id & 0x3FFF_FFFF_FFFF_FFFF) as u64, rand_b);
913    }
914
915    #[test]
916    fn test_public_sub_ms_variants_produce_distinct_layout_options() {
917        let id4 = gen_id_with_sub_ms_4();
918        let id8 = gen_id_with_sub_ms_8();
919        let id12 = gen_id_with_sub_ms_12();
920
921        for id in [id4, id8, id12] {
922            let uuid = uuid::Uuid::from_u128(id);
923            assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
924            assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
925        }
926    }
927
928    fn extract_sub_ms_fraction(id: u128, bits: u8) -> (u64, u16) {
929        let ms = (id >> 80) as u64;
930        let rand_a = ((id >> 64) & 0x0FFF) as u16;
931        let fraction = rand_a >> (12 - bits);
932        (ms, fraction)
933    }
934
935    #[test]
936    fn test_sub_ms_variants_mostly_increase_within_same_millisecond() {
937        let cases: &[(u8, fn() -> u128)] = &[
938            (4, gen_id_with_sub_ms_4),
939            (8, gen_id_with_sub_ms_8),
940            (12, gen_id_with_sub_ms_12),
941        ];
942
943        for &(bits, gen_id) in cases {
944            let start = std::time::Instant::now();
945            let duration = std::time::Duration::from_millis(25);
946            let (mut last_ms, mut last_fraction) = extract_sub_ms_fraction(gen_id(), bits);
947            let mut comparisons = 0usize;
948            let mut nondecreasing = 0usize;
949
950            while start.elapsed() < duration && comparisons < 256 {
951                let (ms, fraction) = extract_sub_ms_fraction(gen_id(), bits);
952                if ms == last_ms {
953                    comparisons += 1;
954                    if fraction >= last_fraction {
955                        nondecreasing += 1;
956                    }
957                }
958
959                last_ms = ms;
960                last_fraction = fraction;
961            }
962
963            assert!(
964                comparisons >= 32,
965                "not enough same-millisecond samples were observed for the {bits}-bit variant"
966            );
967            assert!(
968                nondecreasing * 10 >= comparisons * 9,
969                "sub-ms fraction for the {bits}-bit variant only moved forward in {nondecreasing}/{comparisons} same-millisecond comparisons"
970            );
971        }
972    }
973
974    #[test]
975    fn test_timestamp_updates_continuously() {
976        let start = std::time::Instant::now();
977        let duration = std::time::Duration::from_millis(100);
978
979        let mut last_ts = gen_id() >> 80;
980        let start_ts = last_ts;
981        let mut distinct_timestamps = 0;
982
983        while start.elapsed() < duration {
984            let curr = gen_id();
985            let curr_ts = curr >> 80;
986            if curr_ts > last_ts {
987                distinct_timestamps += 1;
988                last_ts = curr_ts;
989            }
990        }
991
992        let elapsed_ts = last_ts - start_ts;
993        println!(
994            "Timestamp advanced: {} ms, Distinct timestamps observed: {}",
995            elapsed_ts, distinct_timestamps
996        );
997
998        // Expect at least ~100ms of advancement in 100ms real time.
999        // Allow a small margin for runners with coarse timers or scheduling jitter.
1000        assert!(
1001            elapsed_ts >= 98,
1002            "Timestamp should advance roughly 100ms, got {}ms",
1003            elapsed_ts
1004        );
1005
1006        // We should still see many millisecond transitions while spinning in a
1007        // tight loop, but some CI runners (especially Windows) can deschedule
1008        // the test often enough that we miss a noticeable fraction of them.
1009        let min_distinct_timestamps = elapsed_ts.saturating_mul(2) / 3;
1010        assert!(
1011            distinct_timestamps >= min_distinct_timestamps,
1012            "Should see frequent updates, got {} distinct timestamps over {}ms",
1013            distinct_timestamps,
1014            elapsed_ts
1015        );
1016    }
1017
1018    #[test]
1019    fn test_record_time_sample_ignores_older_millisecond_samples() {
1020        let mut state = ThreadState::new();
1021
1022        assert!(state.record_time_sample(
1023            clock::TimestampSample {
1024                ms: 1_000,
1025                nanos_within_ms: 800_000,
1026            },
1027            true
1028        ));
1029        assert!(!state.record_time_sample(
1030            clock::TimestampSample {
1031                ms: 999,
1032                nanos_within_ms: 100_000,
1033            },
1034            true
1035        ));
1036
1037        assert_eq!(state.last_ms, 1_000);
1038        assert_eq!(state.last_nanos_within_ms, 800_000);
1039        assert_eq!(state.last_sampled_nanos_within_ms, 800_000);
1040    }
1041
1042    #[test]
1043    fn test_record_time_sample_keeps_same_millisecond_fraction_monotonic() {
1044        let mut state = ThreadState::new();
1045
1046        assert!(state.record_time_sample(
1047            clock::TimestampSample {
1048                ms: 1_000,
1049                nanos_within_ms: 400_000,
1050            },
1051            true
1052        ));
1053        assert!(!state.record_time_sample(
1054            clock::TimestampSample {
1055                ms: 1_000,
1056                nanos_within_ms: 350_000,
1057            },
1058            false
1059        ));
1060        assert!(!state.record_time_sample(
1061            clock::TimestampSample {
1062                ms: 1_000,
1063                nanos_within_ms: 450_000,
1064            },
1065            true
1066        ));
1067
1068        assert_eq!(state.current_timestamp_sample().nanos_within_ms, 450_000);
1069        assert_eq!(state.last_sampled_nanos_within_ms, 450_000);
1070    }
1071
1072    #[test]
1073    fn test_counter_reset() {
1074        let mut state = ThreadState::new();
1075
1076        assert!(
1077            state.refresh_time(),
1078            "A fresh thread state should accept the first timestamp sample"
1079        );
1080        assert_eq!(
1081            state.counter & !COUNTER_SEED_MASK,
1082            0,
1083            "Seeded counter should start in the low 12 bits"
1084        );
1085    }
1086
1087    #[test]
1088    fn test_counter_rollover_resets_cached_sub_ms_state() {
1089        let mut state = ThreadState::new();
1090        assert!(state.refresh_time());
1091        let previous_ms = state.last_ms;
1092        state.last_nanos_within_ms = 900_000;
1093        state.last_sampled_nanos_within_ms = 900_000;
1094        state.counter = COUNTER_MAX;
1095
1096        let (timestamp, _) = state.get_time_and_counter();
1097
1098        assert_eq!(timestamp, previous_ms + 1);
1099        assert_eq!(state.current_timestamp_sample().nanos_within_ms, 0);
1100        assert_eq!(state.last_sampled_nanos_within_ms, 0);
1101    }
1102}