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, one per thread |
LockSnowflakeGenerator |
โ | โ | Medium | Multi-threaded, high contention |
AtomicSnowflakeGenerator |
โ | โ | Medium | Multi-threaded, low-to-medium contention |
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 ;
let clock = with_epoch;
let mut generator = new;
let id: SnowflakeTwitterId = loop ;
println!;
Or use another pre-built layout such as Mastodon:
use ;
let clock = with_epoch;
let mut generator = new;
// loop as above
Custom Layouts
To define a custom Snowflake layout, implement Snowflake and optionally
Base32:
use ;
// required
// optional, only if you need it
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 ;
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:
- [Apache License, Version 2.0](LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- [MIT License](LICENSE-MIT or https://opensource.org/licenses/MIT)
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.