6 releases
Uses new Rust 2024
new 0.1.5 | May 17, 2025 |
---|---|
0.1.4 | May 15, 2025 |
#628 in Rust patterns
304 downloads per month
86KB
1.5K
SLoC
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 |
---|---|---|---|---|
41 | 10 | 12 | 2010-11-04 01:42:54.657 | |
Discord | 42 | 10 | 12 | 2015-01-01 00:00:00.000 |
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 |
โ | โ | Highest | Single-threaded, zero contention; ideal for sharded/core-local generators |
LockSnowflakeGenerator |
โ | โ | Medium | Multi-threaded workloads where fair access across threads is important |
AtomicSnowflakeGenerator |
โ | โ | High | Multi-threaded workloads where fair access is sacrificed for higher throughput |
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 generatorLockSnowflakeGenerator
: mutex-based, thread-safe generatorAtomicSnowflakeGenerator
: 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.
Dependencies
~0.3โ0.9MB
~19K SLoC