Skip to main content

id_forge/
snowflake.rs

1//! # Snowflake ID generation
2//!
3//! Twitter Snowflake-style 64-bit IDs: 41-bit timestamp + 10-bit
4//! worker ID + 12-bit sequence number. Distributed-safe when each
5//! worker gets a unique ID. Monotonic within a worker.
6//!
7//! In `0.1.0` this is a placeholder implementation.
8
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12/// Default epoch (2026-01-01T00:00:00Z in milliseconds since UNIX epoch).
13pub const DEFAULT_EPOCH_MS: u64 = 1_767_225_600_000;
14
15/// Snowflake ID generator.
16///
17/// Holds the worker ID and the running sequence counter.
18///
19/// # Example
20///
21/// ```
22/// use id_forge::snowflake::Snowflake;
23///
24/// let mut gen = Snowflake::new(1);
25/// let id = gen.next_id();
26/// ```
27#[derive(Debug)]
28pub struct Snowflake {
29    worker_id: u16,
30    epoch_ms: u64,
31    sequence: AtomicU64,
32}
33
34impl Snowflake {
35    /// Build a new generator with the given worker ID (0-1023) and
36    /// the default epoch.
37    pub fn new(worker_id: u16) -> Self {
38        Self::with_epoch(worker_id, DEFAULT_EPOCH_MS)
39    }
40
41    /// Build a new generator with a custom epoch.
42    pub fn with_epoch(worker_id: u16, epoch_ms: u64) -> Self {
43        Self {
44            worker_id: worker_id & 0x3ff,
45            epoch_ms,
46            sequence: AtomicU64::new(0),
47        }
48    }
49
50    /// Generate the next ID.
51    ///
52    /// In `0.1.0` this is a placeholder. The real per-millisecond
53    /// sequence-rollover logic lands in `0.9.x`.
54    pub fn next_id(&self) -> u64 {
55        let now = SystemTime::now()
56            .duration_since(UNIX_EPOCH)
57            .map(|d| d.as_millis() as u64)
58            .unwrap_or(0);
59        let ts = now.saturating_sub(self.epoch_ms) & 0x1ffffffffff; // 41 bits
60        let seq = self.sequence.fetch_add(1, Ordering::Relaxed) & 0xfff; // 12 bits
61        let worker = self.worker_id as u64 & 0x3ff; // 10 bits
62
63        (ts << 22) | (worker << 12) | seq
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn next_id_produces_value() {
73        let gen = Snowflake::new(1);
74        let _ = gen.next_id();
75    }
76
77    #[test]
78    fn worker_id_clamped() {
79        let gen = Snowflake::new(0xffff);
80        let id = gen.next_id();
81        let worker = (id >> 12) & 0x3ff;
82        assert_eq!(worker, 0x3ff);
83    }
84
85    #[test]
86    fn unique_ids() {
87        let gen = Snowflake::new(1);
88        let a = gen.next_id();
89        let b = gen.next_id();
90        assert_ne!(a, b);
91    }
92
93    #[test]
94    fn monotonic_within_ms() {
95        let gen = Snowflake::new(1);
96        let a = gen.next_id();
97        let b = gen.next_id();
98        assert!(b >= a);
99    }
100}