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
TimeSourcetrait - ๐งต Lock-based and lock-free thread-safe ID generation
- ๐ Customizable layouts via the
Snowflaketrait - ๐ข 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
Synchronous
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 ;
let clock = with_epoch;
let generator = new;
let id: SnowflakeTwitterId = loop ;
println!;
Asynchronous
If you're in an async context (e.g., using Tokio), you can
use the async-tokio feature and import the SnowflakeGeneratorAsyncExt trait
to await a new ID:
use ;
async
Custom Layouts
To define a custom Snowflake layout, implement Snowflake:
use fmt;
use Snowflake;
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() (enabled with base32 feature) for
sortable representations:
use ;
let id = from;
println!;
// > default: 517811998762
println!;
// > padded: 00000000517811998762
let encoded = id.encode;
println!;
// > base32: 00000Y4G0082M
let decoded = decode.expect;
assert_eq!;
๐ 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:
๐งช Testing
Run all tests with:
๐ 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.