nexus-id 1.0.0

High-performance ID generators for low-latency systems
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
//! Snowflake-style ID generation with const generic bit layouts.
//!
//! # Overview
//!
//! `nexus-id` provides high-performance snowflake ID generation with
//! user-configurable bit layouts via const generics. No heap allocation,
//! no dependencies, optimized for trading systems where ID generation
//! is on the critical path.
//!
//! The generator is tick-agnostic: callers provide a raw `u64` tick value
//! (milliseconds, block numbers, logical clocks—whatever fits your domain).
//!
//! # Example
//!
//! ```rust
//! use std::time::Instant;
//! use nexus_id::Snowflake64;
//!
//! // Layout: 42 bits tick, 6 bits worker, 16 bits sequence (65k/tick)
//! type ClOrdId = Snowflake64<42, 6, 16>;
//!
//! let epoch = Instant::now();
//! let mut id_gen = ClOrdId::new(5);
//!
//! // In event loop - snap once per iteration
//! let tick = (Instant::now() - epoch).as_millis() as u64;
//! let id: u64 = id_gen.next(tick).unwrap();
//!
//! // Unpack to inspect
//! let (tick, worker, seq) = ClOrdId::unpack(id);
//! assert_eq!(worker, 5);
//! assert_eq!(seq, 0);
//! ```
//!
//! # Bit Layout
//!
//! IDs are packed MSB to LSB as:
//!
//! ```text
//! [timestamp: TS bits][worker: WK bits][sequence: SQ bits]
//! ```
//!
//! Common configurations:
//!
//! | TS | WK | SQ | Years @ ms | Workers | IDs/ms |
//! |----|----|----|------------|---------|--------|
//! | 41 | 10 | 12 | 69         | 1024    | 4k     |
//! | 42 | 6  | 16 | 139        | 64      | 65k    |
//! | 39 | 9  | 16 | 17         | 512     | 65k    |
//!
//! # Performance
//!
//! The `next()` method is designed for hot-path usage:
//! - No syscalls (caller provides tick)
//! - No allocation
//! - Predictable branches (same-tick case is common)
//! - ~24-26 cycles p50 (~6ns at 4GHz)

use core::fmt;
use core::marker::PhantomData;

use crate::snowflake_id::{MixedId32, MixedId64, SnowflakeId32, SnowflakeId64};

/// Integer types usable as snowflake IDs.
///
/// Implemented for `u32`, `i32`, `u64`, `i64`.
///
/// This trait is sealed and cannot be implemented outside this crate.
pub trait IdInt: Copy + private::Sealed {
    /// Number of bits in this integer type.
    const BITS: u8;

    /// Convert from raw u64 representation.
    fn from_raw(v: u64) -> Self;

    /// Convert to raw u64 representation.
    fn to_raw(self) -> u64;
}

mod private {
    pub trait Sealed {}
    impl Sealed for u32 {}
    impl Sealed for i32 {}
    impl Sealed for u64 {}
    impl Sealed for i64 {}
}

macro_rules! impl_id_int {
    ($($ty:ty => $bits:expr),* $(,)?) => {
        $(
            impl IdInt for $ty {
                const BITS: u8 = $bits;

                #[inline(always)]
                fn from_raw(v: u64) -> Self {
                    v as Self
                }

                #[inline(always)]
                fn to_raw(self) -> u64 {
                    self as u64
                }
            }
        )*
    };
}

impl_id_int!(
    u32 => 32,
    i32 => 32,
    u64 => 64,
    i64 => 64,
);

/// Sequence exhausted within the current tick.
///
/// This is a backpressure signal — the caller generated more IDs
/// within a single tick value than the sequence bits allow.
///
/// # Handling
///
/// When this error occurs, the caller should either:
/// - Reject the request (backpressure)
/// - Wait for the next tick (next millisecond, next block, etc.)
/// - Log and investigate (misconfigured sequence bits?)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SequenceExhausted {
    /// Tick value when exhaustion occurred.
    pub tick: u64,
    /// Maximum sequence value for this generator's layout.
    pub max_sequence: u64,
}

impl fmt::Display for SequenceExhausted {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "sequence exhausted at tick {}: generated {} IDs in one tick",
            self.tick,
            self.max_sequence + 1
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for SequenceExhausted {}

/// Snowflake ID generator.
///
/// # Type Parameters
/// - `T`: Output integer type (`u32`, `i32`, `u64`, `i64`)
/// - `TS`: Tick bits (ordering field)
/// - `WK`: Worker bits (0 for single-worker systems)
/// - `SQ`: Sequence bits
///
/// # Layout (MSB to LSB)
/// ```text
/// [tick: TS bits][worker: WK bits][sequence: SQ bits]
/// ```
///
/// # Compile-Time Validation
/// - `TS + WK + SQ <= T::BITS`
/// - `TS > 0`
/// - `SQ > 0`
///
/// # Tick
///
/// The generator is tick-agnostic. The caller provides a raw `u64` value
/// to `next()` which becomes the ordering field in the ID. Common choices:
/// - Milliseconds since an application epoch (`(now - epoch).as_millis() as u64`)
/// - Block number (blockchain)
/// - Logical clock / Lamport counter
///
/// The only requirement is that the value is monotonically non-decreasing.
///
/// # Example
/// ```rust
/// use std::time::Instant;
/// use nexus_id::Snowflake64;
///
/// // 42 bits tick, 6 bits worker, 16 bits sequence
/// type TradingId = Snowflake64<42, 6, 16>;
///
/// let epoch = Instant::now();
/// let mut id_gen = TradingId::new(5);
///
/// let tick = (Instant::now() - epoch).as_millis() as u64;
/// let id: u64 = id_gen.next(tick).unwrap();
/// ```
pub struct Snowflake<T: IdInt, const TS: u8, const WK: u8, const SQ: u8> {
    worker_shifted: u64,
    last_tick: u64,
    sequence: u64,
    _marker: PhantomData<T>,
}

impl<T: IdInt, const TS: u8, const WK: u8, const SQ: u8> Snowflake<T, TS, WK, SQ> {
    /// Compile-time layout validation.
    const _VALIDATE: () = {
        assert!(
            TS as u16 + WK as u16 + SQ as u16 <= T::BITS as u16,
            "layout exceeds integer bits: TS + WK + SQ > T::BITS"
        );
        assert!(TS > 0, "timestamp bits must be > 0");
        assert!(SQ > 0, "sequence bits must be > 0");
    };

    /// Shift amount for timestamp field.
    const TS_SHIFT: u8 = WK + SQ;

    /// Shift amount for worker field.
    const WK_SHIFT: u8 = SQ;

    /// Maximum sequence value for this layout.
    pub const SEQUENCE_MAX: u64 = (1u64 << SQ) - 1;

    /// Maximum worker value for this layout.
    pub const WORKER_MAX: u64 = if WK == 0 { 0 } else { (1u64 << WK) - 1 };

    /// Maximum timestamp value for this layout.
    pub const TIMESTAMP_MAX: u64 = (1u64 << TS) - 1;

    /// Create a new generator.
    ///
    /// # Arguments
    /// * `worker` - Worker ID, must fit in WK bits
    ///
    /// # Panics
    /// Panics if `worker > WORKER_MAX`.
    ///
    /// # Example
    /// ```rust
    /// use nexus_id::Snowflake64;
    ///
    /// type MyId = Snowflake64<42, 6, 16>;
    ///
    /// let mut id_gen = MyId::new(5);
    /// ```
    pub fn new(worker: u64) -> Self {
        // Trigger compile-time validation
        let () = Self::_VALIDATE;

        assert!(
            worker <= Self::WORKER_MAX,
            "worker {} exceeds max {}",
            worker,
            Self::WORKER_MAX
        );

        Self {
            worker_shifted: worker << Self::WK_SHIFT,
            last_tick: u64::MAX, // Ensures first call takes "new tick" branch
            sequence: 0,
            _marker: PhantomData,
        }
    }

    /// Generate the next ID.
    ///
    /// # Arguments
    /// * `tick` - Monotonically non-decreasing value for the ordering field.
    ///   Typically `(now - epoch).as_millis() as u64` for time-based IDs, but can
    ///   be any u64 sequence (block numbers, logical clocks, etc.).
    ///
    /// # Errors
    /// Returns [`SequenceExhausted`] if more than `SEQUENCE_MAX` IDs are
    /// generated with the same tick value. This is a backpressure signal.
    ///
    /// # Performance
    /// This method is designed for hot-path usage:
    /// - No syscalls (caller provides tick)
    /// - No allocation
    /// - Single predictable branch in the common case
    ///
    /// # Example
    /// ```rust
    /// use std::time::Instant;
    /// use nexus_id::Snowflake64;
    ///
    /// type MyId = Snowflake64<42, 6, 16>;
    ///
    /// let epoch = Instant::now();
    /// let mut id_gen = MyId::new(5);
    ///
    /// // Snap time once per event loop iteration
    /// let tick = (Instant::now() - epoch).as_millis() as u64;
    ///
    /// // Generate IDs using that tick
    /// let id1 = id_gen.next(tick).unwrap();
    /// let id2 = id_gen.next(tick).unwrap();
    /// ```
    pub fn next(&mut self, tick: u64) -> Result<T, SequenceExhausted> {
        if tick == self.last_tick {
            self.sequence += 1;
            if self.sequence > Self::SEQUENCE_MAX {
                return Err(SequenceExhausted {
                    tick,
                    max_sequence: Self::SEQUENCE_MAX,
                });
            }
        } else {
            self.last_tick = tick;
            self.sequence = 0;
        }

        Ok(T::from_raw(
            (tick << Self::TS_SHIFT) | self.worker_shifted | self.sequence,
        ))
    }

    /// Generate a raw ID with Fibonacci-mixed bits for identity hashers.
    ///
    /// Applies a Fibonacci multiply (bijective, ~1 cycle) to the raw ID,
    /// producing output with uniform bit distribution. Use with identity
    /// hashers (e.g., `nohash-hasher`) for optimal HashMap performance.
    ///
    /// The mixing is NOT reversible from this raw integer — use
    /// [`next_id()`](Snowflake::next_id) + [`SnowflakeId64::mixed()`] if you
    /// need to unmix later.
    ///
    /// # Example
    ///
    /// ```rust
    /// use nexus_id::Snowflake64;
    ///
    /// type OrderId = Snowflake64<42, 6, 16>;
    ///
    /// let mut id_gen = OrderId::new(0);
    ///
    /// let id = id_gen.mixed(42).unwrap();
    /// ```
    pub fn mixed(&mut self, tick: u64) -> Result<T, SequenceExhausted> {
        let raw = self.next(tick)?;
        Ok(T::from_raw(fibonacci_mix_64(raw.to_raw())))
    }

    /// Unpack an ID into (timestamp, worker, sequence).
    ///
    /// # Example
    /// ```rust
    /// use nexus_id::Snowflake64;
    ///
    /// type MyId = Snowflake64<42, 6, 16>;
    ///
    /// let mut id_gen = MyId::new(5);
    ///
    /// let id = id_gen.next(0).unwrap();
    /// let (ts, worker, seq) = MyId::unpack(id);
    ///
    /// assert_eq!(ts, 0);
    /// assert_eq!(worker, 5);
    /// assert_eq!(seq, 0);
    /// ```
    pub fn unpack(id: T) -> (u64, u64, u64) {
        let raw = id.to_raw();
        (
            raw >> Self::TS_SHIFT,
            (raw >> Self::WK_SHIFT) & Self::WORKER_MAX,
            raw & Self::SEQUENCE_MAX,
        )
    }

    /// Worker ID for this generator.
    #[inline]
    pub const fn worker(&self) -> u64 {
        self.worker_shifted >> Self::WK_SHIFT
    }

    /// Current sequence within this millisecond.
    #[inline]
    pub const fn sequence(&self) -> u64 {
        self.sequence
    }

    /// Last tick value used.
    #[inline]
    pub const fn last_tick(&self) -> u64 {
        self.last_tick
    }
}

// Type aliases for convenience

/// 32-bit snowflake generator.
pub type Snowflake32<const TS: u8, const WK: u8, const SQ: u8> = Snowflake<u32, TS, WK, SQ>;

/// 64-bit snowflake generator.
pub type Snowflake64<const TS: u8, const WK: u8, const SQ: u8> = Snowflake<u64, TS, WK, SQ>;

/// Signed 32-bit snowflake generator.
pub type SnowflakeSigned32<const TS: u8, const WK: u8, const SQ: u8> = Snowflake<i32, TS, WK, SQ>;

/// Signed 64-bit snowflake generator.
pub type SnowflakeSigned64<const TS: u8, const WK: u8, const SQ: u8> = Snowflake<i64, TS, WK, SQ>;

/// Fibonacci multiply for 64-bit hash mixing.
///
/// Bijective permutation (~1 cycle). Spreads structured snowflake bits
/// uniformly across all positions for identity hasher compatibility.
#[inline(always)]
const fn fibonacci_mix_64(x: u64) -> u64 {
    x.wrapping_mul(0x9E37_79B9_7F4A_7C15)
}

// =============================================================================
// Typed ID generation (u64)
// =============================================================================

impl<const TS: u8, const WK: u8, const SQ: u8> Snowflake<u64, TS, WK, SQ> {
    /// Generate the next ID as a typed [`SnowflakeId64`].
    ///
    /// Same as [`next()`](Self::next) but returns the newtype wrapper with
    /// field extraction and mixing methods.
    #[inline]
    pub fn next_id(&mut self, tick: u64) -> Result<SnowflakeId64<TS, WK, SQ>, SequenceExhausted> {
        self.next(tick).map(SnowflakeId64::from_raw)
    }

    /// Generate a Fibonacci-mixed ID as a typed [`MixedId64`].
    ///
    /// The mixed ID can be unmixed back to the original via [`MixedId64::unmix()`].
    #[inline]
    pub fn next_mixed(&mut self, tick: u64) -> Result<MixedId64<TS, WK, SQ>, SequenceExhausted> {
        self.next_id(tick).map(|id| id.mixed())
    }
}

// =============================================================================
// Typed ID generation (u32)
// =============================================================================

impl<const TS: u8, const WK: u8, const SQ: u8> Snowflake<u32, TS, WK, SQ> {
    /// Generate the next ID as a typed [`SnowflakeId32`].
    #[inline]
    pub fn next_id(&mut self, tick: u64) -> Result<SnowflakeId32<TS, WK, SQ>, SequenceExhausted> {
        self.next(tick).map(SnowflakeId32::from_raw)
    }

    /// Generate a Fibonacci-mixed ID as a typed [`MixedId32`].
    #[inline]
    pub fn next_mixed(&mut self, tick: u64) -> Result<MixedId32<TS, WK, SQ>, SequenceExhausted> {
        self.next_id(tick).map(|id| id.mixed())
    }
}

#[cfg(all(test, feature = "std"))]
mod tests {
    use super::*;

    type TestId = Snowflake64<42, 6, 16>;

    #[test]
    fn basic_generation() {
        let mut id_gen = TestId::new(5);

        let id = id_gen.next(0).unwrap();
        let (ts, worker, seq) = TestId::unpack(id);

        assert_eq!(ts, 0);
        assert_eq!(worker, 5);
        assert_eq!(seq, 0);
    }

    #[test]
    fn sequence_increments_same_ts() {
        let mut id_gen = TestId::new(5);

        let id1 = id_gen.next(0).unwrap();
        let id2 = id_gen.next(0).unwrap();
        let id3 = id_gen.next(0).unwrap();

        let (_, _, seq1) = TestId::unpack(id1);
        let (_, _, seq2) = TestId::unpack(id2);
        let (_, _, seq3) = TestId::unpack(id3);

        assert_eq!(seq1, 0);
        assert_eq!(seq2, 1);
        assert_eq!(seq3, 2);
    }

    #[test]
    fn sequence_resets_new_ts() {
        let mut id_gen = TestId::new(5);

        // Generate at timestamp 0
        let _ = id_gen.next(0).unwrap();
        let _ = id_gen.next(0).unwrap();

        // Jump to timestamp 1
        let id = id_gen.next(1).unwrap();

        let (ts, _, seq) = TestId::unpack(id);
        assert_eq!(ts, 1);
        assert_eq!(seq, 0);
    }

    #[test]
    fn worker_encoded_correctly() {
        for worker in [0, 1, 31, 63] {
            let mut id_gen = TestId::new(worker);
            let id = id_gen.next(0).unwrap();
            let (_, w, _) = TestId::unpack(id);
            assert_eq!(w, worker);
        }
    }

    #[test]
    fn ids_are_unique() {
        let mut id_gen = TestId::new(5);

        let mut ids = Vec::new();
        for i in 0..1000u64 {
            // Advance timestamp every 100 IDs
            let ts = i / 100;
            ids.push(id_gen.next(ts).unwrap());
        }

        // Check uniqueness
        let mut sorted = ids.clone();
        sorted.sort_unstable();
        sorted.dedup();
        assert_eq!(sorted.len(), ids.len());
    }

    #[test]
    fn sequence_exhaustion() {
        // Tiny sequence: 4 bits = max 15
        type TinySeq = Snowflake64<42, 6, 4>;

        let mut id_gen = TinySeq::new(5);

        // Generate 16 IDs (0-15)
        for _ in 0..16 {
            id_gen.next(0).unwrap();
        }

        // 17th should fail
        let result = id_gen.next(0);
        assert!(result.is_err());

        let err = result.unwrap_err();
        assert_eq!(err.max_sequence, 15);
    }

    #[test]
    #[should_panic(expected = "worker 100 exceeds max 63")]
    fn worker_overflow_panics() {
        let _id_gen = TestId::new(100); // 6 bits = max 63
    }

    #[test]
    fn signed_output() {
        type SignedId = SnowflakeSigned64<42, 6, 16>;

        let mut id_gen = SignedId::new(5);

        let id: i64 = id_gen.next(0).unwrap();
        let (ts, worker, seq) = SignedId::unpack(id);

        assert_eq!(ts, 0);
        assert_eq!(worker, 5);
        assert_eq!(seq, 0);
    }

    #[test]
    fn small_layout_32bit() {
        // 20 bits timestamp, 4 bits worker, 8 bits sequence
        type SmallId = Snowflake32<20, 4, 8>;

        let mut id_gen = SmallId::new(7);

        let id: u32 = id_gen.next(0).unwrap();
        let (ts, worker, seq) = SmallId::unpack(id);

        assert_eq!(ts, 0);
        assert_eq!(worker, 7);
        assert_eq!(seq, 0);
    }

    #[test]
    fn zero_worker_bits() {
        // Single-worker system: no worker bits
        type SingleWorker = Snowflake64<48, 0, 16>;

        let mut id_gen = SingleWorker::new(0);

        let id = id_gen.next(0).unwrap();
        let (ts, worker, seq) = SingleWorker::unpack(id);

        assert_eq!(ts, 0);
        assert_eq!(worker, 0);
        assert_eq!(seq, 0);
    }

    #[test]
    fn non_time_timestamp() {
        // Use block numbers as timestamp
        type BlockId = Snowflake64<32, 8, 24>;

        let mut id_gen = BlockId::new(1);

        let id1 = id_gen.next(1000).unwrap(); // block 1000
        let id2 = id_gen.next(1000).unwrap(); // same block
        let id3 = id_gen.next(1001).unwrap(); // next block

        let (ts1, _, seq1) = BlockId::unpack(id1);
        let (ts2, _, seq2) = BlockId::unpack(id2);
        let (ts3, _, seq3) = BlockId::unpack(id3);

        assert_eq!(ts1, 1000);
        assert_eq!(seq1, 0);
        assert_eq!(ts2, 1000);
        assert_eq!(seq2, 1);
        assert_eq!(ts3, 1001);
        assert_eq!(seq3, 0);
    }
}