Snowflake-gen 1.0 - Configurable Snowflake ID generator with thread-local global API

Hi all,

I've just published snowflake-gen 1.0.0, a configurable Snowflake ID generator for Rust.

What it does

Generates 63-bit unique IDs using the Snowflake algorithm, with the bit layout fully under your control. You decide the balance between timestamp range, throughput, and node count.

Features

  • Configurable bit layout, tune timestamp, machine ID, node ID, and sequence bit widths (must sum to 63)
  • Thread-local global API, zero-lock ID generation via init() + next_id(), with automatic per-thread node ID assignment
  • Three generation modes , generate() (clock-based, blocks on sequence wrap), real_time_generate() (always reads the clock), lazy_generate() (no clock syscall, fastest)
  • ID decomposition , decode any ID back into its timestamp, machine, node, and sequence components
  • Buffered generation , SnowflakeIdBucket pre-generates a full sequence batch
  • Custom epochs , use any SystemTime as the epoch (Discord, Twitter, or your own)

Quick start

use snowflake_gen::SnowflakeIdGenerator;

let mut idgen = SnowflakeIdGenerator::new(1, 1).unwrap();
let id = idgen.generate().unwrap();

Or with the thread-local global API:

use snowflake_gen::{BitLayout, init, next_id};

init(1, BitLayout::default()).unwrap();
let id = next_id().unwrap();

Default layout (Twitter-compatible)

Field Bits Max value
Timestamp 41 ~69 years
Machine ID 5 31
Node ID 5 31
Sequence 12 4,096/ms

Performance

~21 million IDs/sec across 32 threads with the default layout. The bottleneck is the millisecond clock, each thread exhausts its 4,096-ID sequence in microseconds then spin-waits. If you need more raw throughput, widen sequence_bits or use lazy_generate / SnowflakeIdBucket to skip the clock entirely.

Links

Feedback welcome, especially on the API design and bit layout ergonomics.

Great. Is there any possibility to release thiserror dependency? Just curious, you do bot need to do any move.

For sure, I'm actually considering that.
But I'm wondering: how can I handle errors in a clean way without depending on thiserror?
I don't think my crate really needs it, but I'm open to suggestions or even contributions if you'd like to help!

If you don't want thiserror as a dependency, you just implement Display and Error (and any other features you may be using, like #[from]) yourself. You can expand macros with the nightly compiler, or in the playground under Tools (top-right), if you want to see what the thiserror derive macro generated.

thiserror is basically saving you boilerplate at the cost of longer compile times (separate crate and proc macro invocation). Some consider it worth it, some don't.

Done!

I've just published 1.0.1 as a patch release. I was actually already considering removing thiserror to keep the crate minimal, and your explanation helped me settle on the clean approach without it.

So I replaced it with inline impl blocks for Display and Error instead.

Perfect, thanks.