ferroid 0.1.4

A flexible ID generator for producing unique, monotonic, and lexicographically sortable Snowflake-style IDs.
Documentation

ferroid

ferroid is a Rust crate for generating and parsing Snowflake-style unique IDs.

It supports pre-built layouts for platforms like Twitter, Discord, Instagram, and Mastodon. These IDs are 64-bit integers that encode timestamps, machine/shard IDs, and sequence numbers - making them lexicographically sortable, scalable, and ideal for distributed systems.

Features:

  • ๐Ÿ“Œ Bit-level layout compatibility with major Snowflake formats
  • ๐Ÿงฉ Pluggable time sources via the TimeSource trait
  • ๐Ÿงต Lock-based and lock-free thread-safe ID generation
  • ๐Ÿ“ Customizable layouts via the Snowflake trait
  • ๐Ÿ”ข Lexicographically sortable string encoding

๐Ÿ“ฆ Supported Layouts

Platform Timestamp Bits Machine ID Bits Sequence Bits Epoch
Twitter 41 10 12 2010-11-04 01:42:54.657
Discord 42 10 12 2015-01-01 00:00:00.000
Instagram 41 13 10 2011-01-01 00:00:00.000
Mastodon 48 0 16 1970-01-01 00:00:00.000

๐Ÿ”ง Generator Comparison

Generator Thread-Safe Lock-Free Throughput Use Case
BasicSnowflakeGenerator โŒ โŒ High Single-threaded execution; one instance per thread or core
LockSnowflakeGenerator โœ… โŒ Low Multi-threaded workloads with consistent, shared access
AtomicSnowflakeGenerator โœ… โœ… Medium Multi-threaded workloads with moderate contention; outperforms locks, but not always

All generators produce monotonically increasing, time-ordered, and unique IDs.


๐Ÿš€ Usage

Generate an ID

Calling next_id() may yield Pending if the current sequence is exhausted. In that case, you can spin, yield, or sleep depending on your environment:

use ferroid::{MonotonicClock, TWITTER_EPOCH, BasicSnowflakeGenerator, SnowflakeTwitterId, IdGenStatus};

let clock = MonotonicClock::with_epoch(TWITTER_EPOCH);
let mut generator = BasicSnowflakeGenerator::<SnowflakeTwitterId, _>::new(1, clock);

let id: SnowflakeTwitterId = loop {
    match generator.next_id() {
        IdGenStatus::Ready { id } => break id,
        IdGenStatus::Pending { yield_until } => {
            println!("Exhausted; wait until: {}", yield_until);
            std::hint::spin_loop();
            // Use `std::hint::spin_loop()` for single-threaded or per-thread generators.
            // Use `std::thread::yield_now()` when sharing a generator across multiple threads.
            // Use `tokio::time::sleep().await` in async contexts (e.g., Tokio thread pool).
        }
    }
};

println!("Generated ID: {}", id);

Or use another pre-built layout such as Mastodon:

use ferroid::{MonotonicClock, MASTODON_EPOCH, BasicSnowflakeGenerator, SnowflakeMastodonId, IdGenStatus};

let clock = MonotonicClock::with_epoch(MASTODON_EPOCH);
let mut generator = BasicSnowflakeGenerator::<SnowflakeMastodonId, _>::new(1, clock);

// loop as above

Custom Layouts

To define a custom Snowflake layout, implement Snowflake and optionally Base32:

use ferroid::{Snowflake, Base32};

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyCustomId {
    id: u64,
}

// required
impl Snowflake for MyCustomId {
    // impl required methods
}

// optional, only if you need it
impl Base32 for MyCustomId {}

Behavior

  • If the clock advances: reset sequence to 0 โ†’ IdGenStatus::Ready
  • If the clock is unchanged: increment sequence โ†’ IdGenStatus::Ready
  • If the clock goes backward: return IdGenStatus::Pending
  • If the sequence overflows: return IdGenStatus::Pending

Serialize as padded string

Use .to_padded_string() or .encode() for sortable representations:

use ferroid::{SnowflakeTwitterId};

let id = SnowflakeTwitterId::from(123456, 1, 42);
println!("default: {id}");
// > default: 517811998762

println!("padded: {}", id.to_padded_string());
// > padded: 00000000517811998762

let encoded = id.encode();
println!("base32: {encoded}");
// > base32: 00000Y4G0082M

let decoded = SnowflakeTwitterId::decode(&encoded).expect("decode should succeed");
assert_eq!(id, decoded);

๐Ÿ“ˆ Benchmarks

ferroid ships with Criterion benchmarks to measure ID generation performance:

  • BasicSnowflakeGenerator: single-threaded generator
  • LockSnowflakeGenerator: mutex-based, thread-safe generator
  • AtomicSnowflakeGenerator: lock-free, thread-safe generator

Benchmark scenarios include:

  • Single-threaded with/without a real clock
  • Multi-threaded with/without a real clock

NOTE: Shared generators (like LockSnowflakeGenerator and AtomicSnowflakeGenerator) can slow down under high thread contention. This happens because threads must coordinate access - either through mutex locks or atomic compare-and-swap (CAS) loops - which introduces overhead.

For maximum throughput, avoid sharing. Instead, give each thread its own generator instance. This eliminates contention and allows every thread to issue IDs independently at full speed.

The thread-safe generators are primarily for convenience, or for use cases where ID generation is not expected to be the performance bottleneck. To run:

cargo criterion

๐Ÿงช Testing

Run all tests with:

cargo test --all-features

๐Ÿ“„ License

Licensed under either of:

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.