ferroid/id/
ulid.rs

1use core::hash::Hash;
2
3use crate::id::Id;
4
5/// Trait for layout-compatible ULID-style identifiers.
6///
7/// This trait abstracts a `timestamp`, `random` , and `sequence` partitions
8/// over a fixed-size integer (e.g., `u128`) used for high-entropy time-sortable
9/// ID generation.
10///
11/// Types implementing `UlidId` expose methods for construction, encoding, and
12/// extracting field components from packed integers.
13pub trait UlidId: Id {
14    /// Returns the timestamp portion of the ID.
15    fn timestamp(&self) -> Self::Ty;
16
17    /// Returns the random portion of the ID.
18    fn random(&self) -> Self::Ty;
19
20    /// Returns the maximum possible value for the timestamp field.
21    fn max_timestamp() -> Self::Ty;
22
23    /// Returns the maximum possible value for the random field.
24    fn max_random() -> Self::Ty;
25
26    /// Constructs a new ULID from its components.
27    #[must_use]
28    fn from_components(timestamp: Self::Ty, random: Self::Ty) -> Self;
29
30    /// Returns true if the current sequence value can be incremented.
31    fn has_random_room(&self) -> bool {
32        self.random() < Self::max_random()
33    }
34
35    /// Returns the next sequence value.
36    fn next_random(&self) -> Self::Ty {
37        self.random() + Self::ONE
38    }
39
40    /// Returns a new ID with the random portion incremented.
41    #[must_use]
42    fn increment_random(&self) -> Self {
43        Self::from_components(self.timestamp(), self.next_random())
44    }
45
46    /// Returns a new ID for a newer timestamp with sequence reset to zero.
47    #[must_use]
48    fn rollover_to_timestamp(&self, ts: Self::Ty, rand: Self::Ty) -> Self {
49        Self::from_components(ts, rand)
50    }
51
52    /// Returns `true` if the ID's internal structure is valid, such as reserved
53    /// bits being unset or fields within expected ranges.
54    fn is_valid(&self) -> bool;
55
56    /// Returns a normalized version of the ID with any invalid or reserved bits
57    /// cleared. This guarantees a valid, canonical representation.
58    #[must_use]
59    fn into_valid(self) -> Self;
60}
61
62/// A macro for defining a bit layout for a custom Ulid using three required
63/// components: `reserved`, `timestamp`, and `random`.
64///
65/// These components are always laid out from **most significant bit (MSB)** to
66/// **least significant bit (LSB)** - in that exact order.
67///
68/// - The first field (`reserved`) occupies the highest bits.
69/// - The last field (`random`) occupies the lowest bits.
70/// - The total number of bits **must exactly equal** the size of the backing
71///   integer type (`u64`, `u128`, etc.). If it doesn't, the macro will trigger
72///   a compile-time assertion failure.
73///
74/// ```text
75/// define_ulid!(
76///     <TypeName>, <IntegerType>,
77///     reserved: <bits>,
78///     timestamp: <bits>,
79///     random: <bits>
80/// );
81/// ```
82///
83/// ## Example: A non-monotonic ULID layout
84/// ```rust
85/// use ferroid::define_ulid;
86///
87/// define_ulid!(
88///     MyCustomId, u128,
89///     reserved: 0,
90///     timestamp: 48,
91///     random: 80
92/// );
93/// ```
94///
95/// Which expands to the following bit layout:
96///
97/// ```text
98///  Bit Index:  127            80 79           0
99///              +----------------+-------------+
100///  Field:      | timestamp (48) | random (80) |
101///              +----------------+-------------+
102///              |<-- MSB -- 128 bits -- LSB -->|
103/// ```
104#[cfg_attr(docsrs, doc(cfg(feature = "ulid")))]
105#[macro_export]
106macro_rules! define_ulid {
107    (
108        $(#[$meta:meta])*
109        $name:ident, $int:ty,
110        reserved: $reserved_bits:expr,
111        timestamp: $timestamp_bits:expr,
112        random: $random_bits:expr
113    ) => {
114        $(#[$meta])*
115        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
116        #[repr(transparent)]
117        pub struct $name {
118            id: $int,
119        }
120
121        const _: () = {
122            // Compile-time check: total bit width _must_ equal the backing
123            // type. This is to avoid aliasing surprises.
124            assert!(
125                $reserved_bits + $timestamp_bits + $random_bits == <$int>::BITS,
126                "Layout must match underlying type width"
127            );
128        };
129
130
131        impl $name {
132            pub const RESERVED_BITS: $int = $reserved_bits;
133            pub const TIMESTAMP_BITS: $int = $timestamp_bits;
134            pub const RANDOM_BITS: $int = $random_bits;
135
136            pub const RANDOM_SHIFT: $int = 0;
137            pub const TIMESTAMP_SHIFT: $int = Self::RANDOM_SHIFT + Self::RANDOM_BITS;
138            pub const RESERVED_SHIFT: $int = Self::TIMESTAMP_SHIFT + Self::TIMESTAMP_BITS;
139
140            pub const RESERVED_MASK: $int = ((1 << Self::RESERVED_BITS) - 1);
141            pub const TIMESTAMP_MASK: $int = ((1 << Self::TIMESTAMP_BITS) - 1);
142            pub const RANDOM_MASK: $int = ((1 << Self::RANDOM_BITS) - 1);
143
144            const fn valid_mask() -> $int {
145                (Self::TIMESTAMP_MASK << Self::TIMESTAMP_SHIFT) |
146                (Self::RANDOM_MASK << Self::RANDOM_SHIFT)
147            }
148
149            #[must_use]
150            pub const fn from(timestamp: $int, random: $int) -> Self {
151                let t = (timestamp & Self::TIMESTAMP_MASK) << Self::TIMESTAMP_SHIFT;
152                let r = (random & Self::RANDOM_MASK) << Self::RANDOM_SHIFT;
153                Self { id: t | r }
154            }
155
156            /// Extracts the timestamp from the packed ID.
157            #[must_use]
158            pub const fn timestamp(&self) -> $int {
159                (self.id >> Self::TIMESTAMP_SHIFT) & Self::TIMESTAMP_MASK
160            }
161            /// Extracts the random number from the packed ID.
162            #[must_use]
163            pub const fn random(&self) -> $int {
164                (self.id >> Self::RANDOM_SHIFT) & Self::RANDOM_MASK
165            }
166            /// Returns the maximum representable timestamp value based on
167            /// `Self::TIMESTAMP_BITS`.
168            #[must_use]
169            pub const fn max_timestamp() -> $int {
170                Self::TIMESTAMP_MASK
171            }
172            /// Returns the maximum representable randome value based on
173            /// `Self::RANDOM_BIT`.
174            #[must_use]
175            pub const fn max_random() -> $int {
176                Self::RANDOM_MASK
177            }
178
179            /// Converts this type into its raw type representation
180            #[must_use]
181            pub const fn to_raw(&self) -> $int {
182                self.id
183            }
184
185            /// Converts a raw type into this type
186            #[must_use]
187            pub const fn from_raw(raw: $int) -> Self {
188                Self { id: raw }
189            }
190
191            /// Generates a non-monotonic ULID using the current system time in
192            /// milliseconds since the Unix epoch and the built-in
193            /// [`ThreadRandom`] random generator.
194            ///
195            /// This convenience constructor does **not** maintain any internal
196            /// state and therefore does *not* guarantee monotonicity when
197            /// multiple IDs are created within the same millisecond. If you
198            /// have a bursty load or need strictly monotonic ULIDs, prefer a
199            /// stateful generator such as [`BasicUlidGenerator`] or
200            /// [`BasicMonoUlidGenerator`].
201            ///
202            /// Internally, this performs a system time query on every call,
203            /// making it the slowest way to generate a ULID, but it is suitable
204            /// for low-volume or one-off ID generation.
205            ///
206            /// [`ThreadRandom`]: crate::rand::ThreadRandom
207            /// [`BasicUlidGenerator`]: crate::generator::BasicUlidGenerator
208            /// [`BasicMonoUlidGenerator`]:
209            ///     crate::generator::BasicMonoUlidGenerator
210            #[cfg(feature = "std")]
211            #[must_use]
212            pub fn now() -> Self {
213                #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
214                {
215                    use web_time::web::SystemTimeExt;
216                    Self::from_datetime(web_time::SystemTime::now().to_std())
217                }
218                #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
219                {
220                    Self::from_datetime(std::time::SystemTime::now())
221                }
222            }
223
224            /// Returns this ULID's timestamp as a [`std::time::SystemTime`].
225            ///
226            /// The ULID timestamp encodes the number of milliseconds since
227            /// [`std::time::UNIX_EPOCH`].
228            ///
229            /// # ⚠️ Note
230            /// The precision is limited to whole milliseconds, matching the
231            /// ULID specification.
232            #[cfg(feature = "std")]
233            #[must_use]
234            pub fn datetime(&self) -> std::time::SystemTime {
235                std::time::SystemTime::UNIX_EPOCH
236                    + std::time::Duration::from_millis(self.timestamp() as u64)
237            }
238
239            /// Generates a ULID from the given timestamp in milliseconds since
240            /// UNIX epoch, using the built-in [`ThreadRandom`] random
241            /// generator.
242            ///
243            /// [`ThreadRandom`]: crate::rand::ThreadRandom
244            #[cfg(feature = "std")]
245            #[must_use]
246            pub fn from_timestamp(timestamp: <Self as $crate::id::Id>::Ty) -> Self {
247                Self::from_timestamp_and_rand(timestamp, &$crate::rand::ThreadRandom)
248            }
249
250            /// Generates a ULID from the given timestamp in milliseconds since
251            /// UNIX epoch and a custom random number generator implementing
252            /// [`RandSource`]
253            ///
254            /// [`RandSource`]: crate::rand::RandSource
255            #[must_use]
256            pub fn from_timestamp_and_rand<R>(timestamp: <Self as $crate::id::Id>::Ty, rng: &R) -> Self
257            where
258                R: $crate::rand::RandSource<<Self as $crate::id::Id>::Ty>,
259            {
260                let random = rng.rand();
261                Self::from(timestamp, random)
262            }
263
264            /// Generates a ULID from the given `SystemTime`, using the built-in
265            /// [`ThreadRandom`] random generator.
266            ///
267            /// [`ThreadRandom`]: crate::rand::ThreadRandom
268            #[cfg(feature = "std")]
269            #[must_use]
270            pub fn from_datetime(datetime: std::time::SystemTime) -> Self {
271                Self::from_datetime_and_rand(datetime, &$crate::rand::ThreadRandom)
272            }
273
274            /// Generates a ULID from the given `SystemTime` and a custom random
275            /// number generator implementing [`RandSource`]
276            ///
277            /// [`RandSource`]: crate::rand::RandSource
278            ///
279            #[cfg(feature = "std")]
280            #[must_use]
281            pub fn from_datetime_and_rand<R>(datetime: std::time::SystemTime, rng: &R) -> Self
282            where
283                R: $crate::rand::RandSource<<Self as $crate::id::Id>::Ty>,
284            {
285                let timestamp = datetime
286                    .duration_since(std::time::SystemTime::UNIX_EPOCH)
287                    .unwrap_or(core::time::Duration::ZERO)
288                    .as_millis();
289                let random = rng.rand();
290                Self::from(timestamp, random)
291            }
292        }
293
294        impl $crate::id::Id for $name {
295            type Ty = $int;
296            const ZERO: $int = 0;
297            const ONE: $int = 1;
298
299            /// Converts this type into its raw type representation
300            fn to_raw(&self) -> Self::Ty {
301                self.to_raw()
302            }
303
304            /// Converts a raw type into this type
305            fn from_raw(raw: Self::Ty) -> Self {
306                Self::from_raw(raw)
307            }
308        }
309
310        impl $crate::id::UlidId for $name {
311            fn timestamp(&self) -> Self::Ty {
312                self.timestamp()
313            }
314
315            fn random(&self) -> Self::Ty {
316                self.random()
317            }
318
319            fn max_timestamp() -> Self::Ty {
320                Self::TIMESTAMP_MASK
321            }
322
323            fn max_random() -> Self::Ty {
324                Self::RANDOM_MASK
325            }
326
327            fn from_components(timestamp: $int, random: $int) -> Self {
328                // Random bits can frequencly overflow, but this is okay since
329                // they're masked. We don't need a debug assertion here because
330                // this is expected behavior. However, the timestamp should
331                // never overflow.
332                debug_assert!(timestamp <= Self::TIMESTAMP_MASK, "timestamp overflow");
333                Self::from(timestamp, random)
334            }
335
336            fn is_valid(&self) -> bool {
337                (self.to_raw() & !Self::valid_mask()) == 0
338            }
339
340            fn into_valid(self) -> Self {
341                let raw = self.to_raw() & Self::valid_mask();
342                Self::from_raw(raw)
343            }
344        }
345
346        $crate::cfg_base32! {
347            impl core::fmt::Display for $name {
348                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
349                    use $crate::base32::Base32UlidExt;
350                    self.encode().fmt(f)
351                }
352            }
353
354            impl core::convert::TryFrom<&str> for $name {
355                type Error = $crate::base32::Error<$name>;
356
357                fn try_from(s: &str) -> Result<Self, Self::Error> {
358                    use $crate::base32::Base32UlidExt;
359                    Self::decode(s)
360                }
361            }
362
363            impl core::str::FromStr for $name {
364                type Err = $crate::base32::Error<$name>;
365
366                fn from_str(s: &str) -> Result<Self, Self::Err> {
367                    use $crate::base32::Base32UlidExt;
368                    Self::decode(s)
369                }
370            }
371        }
372
373        impl core::fmt::Debug for $name {
374            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375                let full = core::any::type_name::<Self>();
376                let name = full.rsplit("::").next().unwrap_or(full);
377                let mut dbg = f.debug_struct(name);
378                dbg.field("id", &format_args!("{:} (0x{:x})", self.to_raw(), self.to_raw()));
379                dbg.field("timestamp", &format_args!("{:} (0x{:x})", self.timestamp(), self.timestamp()));
380                dbg.field("random", &format_args!("{:} (0x{:x})", self.random(), self.random()));
381                dbg.finish()
382            }
383        }
384    };
385}
386
387define_ulid!(
388    /// A 128-bit ULID
389    ///
390    /// - 0 bits reserved
391    /// - 48 bits timestamp
392    /// - 80 bits random
393    ///
394    /// ```text
395    ///  Bit Index:  127            80 79           0
396    ///              +----------------+-------------+
397    ///  Field:      | timestamp (48) | random (80) |
398    ///              +----------------+-------------+
399    ///              |<-- MSB -- 128 bits -- LSB -->|
400    /// ```
401    ULID, u128,
402    reserved: 0,
403    timestamp: 48,
404    random: 80
405);
406
407#[cfg(all(test, feature = "std"))]
408mod tests {
409    use std::println;
410
411    use super::*;
412    use crate::rand::RandSource;
413
414    struct MockRand;
415    impl RandSource<u128> for MockRand {
416        fn rand(&self) -> u128 {
417            42
418        }
419    }
420
421    #[test]
422    fn ulid_validity() {
423        let id = ULID::from_raw(u128::MAX);
424        assert!(id.is_valid());
425        let valid = id.into_valid();
426        assert!(valid.is_valid());
427    }
428
429    #[test]
430    fn test_ulid_id_fields_and_bounds() {
431        let ts = ULID::max_timestamp();
432        let rand = ULID::max_random();
433
434        let id = ULID::from(ts, rand);
435        println!("ID: {id:#?}");
436        assert_eq!(id.timestamp(), ts);
437        assert_eq!(id.random(), rand);
438        assert_eq!(ULID::from_components(ts, rand), id);
439    }
440
441    #[test]
442    fn ulid_low_bit_fields() {
443        let id = ULID::from_components(0, 0);
444        assert_eq!(id.timestamp(), 0);
445        assert_eq!(id.random(), 0);
446
447        let id = ULID::from_components(1, 1);
448        assert_eq!(id.timestamp(), 1);
449        assert_eq!(id.random(), 1);
450    }
451
452    #[test]
453    fn ulid_from_timestamp() {
454        let id = ULID::from_timestamp(0);
455        assert_eq!(id.timestamp(), 0);
456
457        let id = ULID::from_timestamp(ULID::max_timestamp());
458        assert_eq!(id.timestamp(), ULID::max_timestamp());
459    }
460
461    #[test]
462    fn ulid_from_timestamp_and_rand() {
463        let id = ULID::from_timestamp_and_rand(42, &MockRand);
464        assert_eq!(id.timestamp(), 42);
465        assert_eq!(id.random(), 42);
466    }
467
468    #[test]
469    fn ulid_from_datetime() {
470        let id = ULID::from_datetime(std::time::SystemTime::UNIX_EPOCH);
471        assert_eq!(id.timestamp(), 0);
472
473        let id = ULID::from_datetime(
474            std::time::SystemTime::UNIX_EPOCH + core::time::Duration::from_millis(1000),
475        );
476        assert_eq!(id.timestamp(), 1000);
477    }
478
479    #[test]
480    fn ulid_from_datetime_and_rand() {
481        let id = ULID::from_datetime_and_rand(
482            std::time::SystemTime::UNIX_EPOCH + core::time::Duration::from_millis(42),
483            &MockRand,
484        );
485        assert_eq!(id.timestamp(), 42);
486        assert_eq!(id.random(), 42);
487    }
488}