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}