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
//! # SCRU64: Sortable, Clock-based, Realm-specifically Unique identifier
//!
//! SCRU64 ID offers compact, time-ordered unique identifiers generated by
//! distributed nodes. SCRU64 has the following features:
//!
//! - ~62-bit non-negative integer storable as signed/unsigned 64-bit integer
//! - Sortable by generation time (as integer and as text)
//! - 12-digit case-insensitive textual representation (Base36)
//! - ~38-bit Unix epoch-based timestamp that ensures useful life until year 4261
//! - Variable-length node/machine ID and counter fields that share 24 bits
//!
//! ```rust
//! // pass node ID through environment variable
//! std::env::set_var("SCRU64_NODE_SPEC", "42/8");
//!
//! // generate a new identifier object
//! let x = scru64::new();
//! println!("{}", x); // e.g., "0u2r85hm2pt3"
//! println!("{}", x.to_u64()); // as a 64-bit unsigned integer
//!
//! // generate a textual representation directly
//! println!("{}", scru64::new_string()); // e.g., "0u2r85hm2pt4"
//! ```
//!
//! See [SCRU64 Specification] for details.
//!
//! SCRU64's uniqueness is realm-specific, i.e., dependent on the centralized
//! assignment of node ID to each generator. If you need decentralized, globally
//! unique time-ordered identifiers, consider [SCRU128].
//!
//! [SCRU64 Specification]: https://github.com/scru64/spec
//! [SCRU128]: https://github.com/scru128/spec
//!
//! ## Crate features
//!
//! Default features:
//!
//! - `std` integrates the library with, among others, the system clock to draw
//! current timestamps. Without `std`, this crate provides limited functionality
//! available under `no_std` environments.
//! - `global_gen` (implies `std`) enables the primary [`new()`] and [`new_string()`]
//! functions and the process-wide global generator under the hood.
//!
//! Optional features:
//!
//! - `serde` enables serialization/deserialization via serde.
//! - `tokio` (together with `global_gen`) enables the [`async_tokio::new()`] and
//! [`async_tokio::new_string()`] functions, the non-blocking counterpart of [`new()`]
//! and [`new_string()`].
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod generator;
mod identifier;
pub use identifier::{ParseError, PartsError, RangeError, Scru64Id};
#[cfg(feature = "global_gen")]
pub use shortcut::{new, new_string};
#[cfg(all(feature = "global_gen", feature = "tokio"))]
pub use shortcut::async_tokio;
#[cfg(test)]
mod test_cases;
/// The total size in bits of the `node_id` and `counter` fields.
const NODE_CTR_SIZE: u8 = 24;
#[cfg(feature = "global_gen")]
#[cfg_attr(docsrs, doc(cfg(feature = "global_gen")))]
mod shortcut {
use std::{thread, time};
use crate::{generator::GlobalGenerator, Scru64Id};
const DELAY: time::Duration = time::Duration::from_millis(64);
/// Generates a new SCRU64 ID object using the global generator.
///
/// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
/// environment variable by default, and it panics if it fails to read a well-formed node spec
/// string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method is first
/// called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec string format.
///
/// This function usually returns a value immediately, but if not possible, it sleeps and waits
/// for the next timestamp tick. It employs blocking sleep to wait; see [`async_tokio::new`]
/// for the non-blocking equivalent.
///
/// # Panics
///
/// Panics if the global generator is not properly configured.
pub fn new() -> Scru64Id {
loop {
if let Some(value) = GlobalGenerator.generate() {
break value;
} else {
thread::sleep(DELAY);
}
}
}
/// Generates a new SCRU64 ID encoded in the 12-digit canonical string representation using the
/// global generator.
///
/// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
/// environment variable by default, and it panics if it fails to read a well-formed node spec
/// string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method is first
/// called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec string format.
///
/// This function usually returns a value immediately, but if not possible, it sleeps and waits
/// for the next timestamp tick. It employs blocking sleep to wait; see
/// [`async_tokio::new_string`] for the non-blocking equivalent.
///
/// # Panics
///
/// Panics if the global generator is not properly configured.
pub fn new_string() -> String {
new().into()
}
/// Generates 100k monotonically increasing IDs.
#[cfg(test)]
#[test]
fn test() {
std::env::set_var("SCRU64_NODE_SPEC", "42/8");
let mut prev = new_string();
for _ in 0..100_000 {
let curr = new_string();
assert!(prev < curr);
prev = curr;
}
}
/// Non-blocking global generator functions using `tokio`.
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub mod async_tokio {
use super::{GlobalGenerator, Scru64Id, DELAY};
/// Generates a new SCRU64 ID object using the global generator.
///
/// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
/// environment variable by default, and it panics if it fails to read a well-formed node
/// spec string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method
/// is first called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec
/// string format.
///
/// This function usually returns a value immediately, but if not possible, it sleeps and
/// waits for the next timestamp tick.
///
/// # Panics
///
/// Panics if the global generator is not properly configured.
pub async fn new() -> Scru64Id {
loop {
if let Some(value) = GlobalGenerator.generate() {
break value;
} else {
tokio::time::sleep(DELAY).await;
}
}
}
/// Generates a new SCRU64 ID encoded in the 12-digit canonical string representation using
/// the global generator.
///
/// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
/// environment variable by default, and it panics if it fails to read a well-formed node
/// spec string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method
/// is first called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec
/// string format.
///
/// This function usually returns a value immediately, but if not possible, it sleeps and
/// waits for the next timestamp tick.
///
/// # Panics
///
/// Panics if the global generator is not properly configured.
pub async fn new_string() -> String {
loop {
if let Some(value) = GlobalGenerator.generate() {
break value.into();
} else {
tokio::time::sleep(DELAY).await;
}
}
}
/// Generates 100k monotonically increasing IDs.
#[cfg(test)]
#[tokio::test]
async fn test() {
std::env::set_var("SCRU64_NODE_SPEC", "42/8");
let mut prev = new_string().await;
for _ in 0..100_000 {
let curr = new_string().await;
assert!(prev < curr);
prev = curr;
}
}
}
}