| Crates.io | nexus-bits |
| lib.rs | nexus-bits |
| version | 0.3.0 |
| created_at | 2026-01-07 17:09:57.969406+00 |
| updated_at | 2026-01-07 20:58:48.41488+00 |
| description | Bit field packing for integer IDs |
| homepage | |
| repository | https://github.com/Abso1ut3Zer0/nexus |
| max_upload_size | |
| id | 2028673 |
| size | 134,304 |
nexus-bits provides derive macros for packing and unpacking integers as bit fields. Unlike other bitfield libraries that generate structs containing integers, nexus-bits generates newtypes that are the integer—ideal for wire protocols, database IDs, and trading systems where the packed integer is the canonical representation.
[dependencies]
nexus-bits = "0.3"
Result#[repr(transparent)] newtypesu8, u16, u32, u64, u128 and signed variantsPack multiple fields into a single integer:
use nexus_bits::bit_storage;
#[bit_storage(repr = u64)]
pub struct SnowflakeId {
#[field(start = 0, len = 12)]
sequence: u16,
#[field(start = 12, len = 10)]
worker: u16,
#[field(start = 22, len = 42)]
timestamp: u64,
}
// Build with validation
let id = SnowflakeId::builder()
.sequence(100)
.worker(5)
.timestamp(1234567890)
.build()?;
// Accessors
assert_eq!(id.sequence(), 100);
assert_eq!(id.worker(), 5);
assert_eq!(id.timestamp(), 1234567890);
// Wire conversion
let raw: u64 = id.raw();
let parsed = SnowflakeId::from_raw(raw);
Single-bit boolean fields:
use nexus_bits::bit_storage;
#[bit_storage(repr = u8)]
pub struct OrderFlags {
#[flag(0)]
is_buy: bool,
#[flag(1)]
is_hidden: bool,
#[flag(2)]
is_post_only: bool,
#[field(start = 4, len = 4)]
priority: u8,
}
let flags = OrderFlags::builder()
.is_buy(true)
.is_hidden(false)
.is_post_only(true)
.priority(7)
.build()?;
assert!(flags.is_buy());
assert!(!flags.is_hidden());
assert!(flags.is_post_only());
assert_eq!(flags.priority(), 7);
Integer-backed enums for use in bit fields:
use nexus_bits::IntEnum;
#[derive(IntEnum, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Side {
Buy = 0,
Sell = 1,
}
#[derive(IntEnum, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TimeInForce {
Day = 0,
Gtc = 1,
Ioc = 2,
Fok = 3,
}
// Use in bit_storage
#[bit_storage(repr = u32)]
pub struct OrderInfo {
#[field(start = 0, len = 1)]
side: Side,
#[field(start = 1, len = 2)]
tif: TimeInForce,
#[field(start = 3, len = 16)]
quantity: u16,
}
let order = OrderInfo::builder()
.side(Side::Buy)
.tif(TimeInForce::Ioc)
.quantity(100)
.build()?;
// IntEnum accessors return Result (discriminant might be invalid from wire)
assert_eq!(order.side()?, Side::Buy);
assert_eq!(order.tif()?, TimeInForce::Ioc);
assert_eq!(order.quantity(), 100); // Primitives are infallible
Different interpretations of the same bits based on a discriminant:
use nexus_bits::{bit_storage, IntEnum};
#[derive(IntEnum, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Exchange { Nasdaq = 0, Nyse = 1, Cboe = 2 }
#[bit_storage(repr = i64, discriminant(start = 0, len = 4))]
pub enum InstrumentId {
#[variant(0)]
Equity {
#[field(start = 4, len = 8)]
exchange: Exchange,
#[field(start = 12, len = 20)]
symbol: u32,
},
#[variant(1)]
Future {
#[field(start = 4, len = 8)]
exchange: Exchange,
#[field(start = 12, len = 16)]
underlying: u16,
#[field(start = 28, len = 16)]
expiry: u16,
},
#[variant(2)]
Option {
#[field(start = 4, len = 8)]
exchange: Exchange,
#[field(start = 12, len = 16)]
underlying: u16,
#[field(start = 28, len = 16)]
expiry: u16,
#[field(start = 44, len = 16)]
strike: u16,
#[flag(60)]
is_call: bool,
},
}
// Build a variant
let equity = InstrumentId::equity()
.exchange(Exchange::Nasdaq)
.symbol(12345)
.build()?;
// Variant accessors are infallible (pre-validated at build time)
assert_eq!(equity.exchange(), Exchange::Nasdaq);
assert_eq!(equity.symbol(), 12345);
// Convert to wire type
let wire: InstrumentId = equity.into();
let raw: i64 = wire.raw();
// Parse from wire and dispatch by kind
let parsed = InstrumentId::from_raw(raw);
// Check variant
assert!(parsed.is_equity());
assert!(!parsed.is_future());
// Match on kind
match parsed.kind()? {
InstrumentIdKind::Equity => {
let e = parsed.as_equity()?;
println!("Equity symbol: {}", e.symbol());
}
InstrumentIdKind::Future => {
let f = parsed.as_future()?;
println!("Future expiry: {}", f.expiry());
}
InstrumentIdKind::Option => {
let o = parsed.as_option()?;
println!("Option strike: {}", o.strike());
}
}
Given #[bit_storage(repr = u64)] struct Foo { ... }:
| Type | Description |
|---|---|
Foo |
#[repr(transparent)] newtype with from_raw(), raw(), field accessors |
FooBuilder |
Builder with setters and build() -> Result<Foo, FieldOverflow<u64>> |
Given #[bit_storage(repr = i64, discriminant(...))] enum Foo { Bar { ... }, Baz { ... } }:
| Type | Description |
|---|---|
Foo |
Parent wire type with from_raw(), raw(), kind(), is_*(), as_*() |
FooBar |
Validated variant type with infallible accessors |
FooBaz |
Validated variant type with infallible accessors |
FooBarBuilder |
Builder with build() and build_parent() |
FooBazBuilder |
Builder with build() and build_parent() |
FooKind |
Discriminant enum (FooKind::Bar, FooKind::Baz) |
use nexus_bits::{FieldOverflow, UnknownDiscriminant, Overflow};
// Returned by builders when a value exceeds field capacity
let err: FieldOverflow<u64> = FieldOverflow {
field: "sequence",
overflow: Overflow { value: 5000, max: 4095 },
};
// Returned by kind() / as_*() for invalid discriminant or IntEnum
let err: UnknownDiscriminant<u64> = UnknownDiscriminant {
field: "__discriminant",
value: 0x1234567890,
};
The macro rejects invalid configurations at compile time:
| Error | Example | Message |
|---|---|---|
| Overlapping fields | Two fields both use bits 0-7 | "field 'b' overlaps with 'a'" |
| Field exceeds repr | 16-bit field at bit 60 in u64 | "field exceeds 64 bits (start 60 + len 16 = 76)" |
| Flag out of bounds | #[flag(64)] in u64 |
"flag bit 64 exceeds 64 bits" |
| Zero-length field | len = 0 |
"len must be > 0" |
| Discriminant overflow | 4-bit discriminant with #[variant(20)] |
"variant discriminant 20 exceeds max 15" |
| Duplicate discriminant | Two variants with #[variant(0)] |
"duplicate discriminant 0: already used by 'Foo'" |
| Field overlaps discriminant | Field at bits 0-7, discriminant at bits 0-3 | "field 'x' overlaps with discriminant" |
Gaps between fields are allowed (reserved bits, padding).
| Feature | nexus-bits | modular-bitfield | bitfield-struct | packed_struct |
|---|---|---|---|---|
| Flat structs | ✅ | ✅ | ✅ | ✅ |
| Tagged enums | ✅ | ❌ | ❌ | Partial |
| Validated variant types | ✅ | N/A | N/A | ❌ |
| IntEnum in fields | ✅ | ❌ | ❌ | ✅ |
| Builder pattern | ✅ | ❌ | ❌ | ❌ |
| Overflow detection | Result |
Silent truncation | Silent truncation | Varies |
| Zero-cost newtype | ✅ | ❌ (generates struct) | ❌ | ❌ |
Wire protocols / message formats: The integer IS the data. You receive an i64 instrument ID over the wire and need to interpret its bits differently based on a discriminant.
// nexus-bits: the i64 is your type
let id = InstrumentId::from_raw(wire_value);
match id.kind()? { ... }
// Other libraries: wrapper around storage
let id = InstrumentId::from_bytes(&wire_value.to_le_bytes());
Trading systems: Packing order flags, instrument IDs, snowflake IDs where:
ID generation: Snowflake-style IDs where you pack timestamp, worker, sequence into a single integer and need both packing and unpacking.
modular-bitfield: Hardware registers, memory-mapped I/O where you're manipulating a struct in place and don't need tagged unions.
bitvec: Arbitrary-length bit arrays, bit-level slicing, when you need more than 128 bits.
packed_struct: Byte-oriented serialization with endianness control, protocol buffers style packing.
Most bitfield libraries generate a struct that contains an integer:
// modular-bitfield style
#[bitfield]
struct Flags {
a: B4,
b: B4,
}
let f = Flags::new().with_a(1).with_b(2);
let raw: u8 = f.into_bytes()[0]; // Extract the integer
nexus-bits generates a newtype that is the integer:
// nexus-bits style
#[bit_storage(repr = u8)]
struct Flags {
#[field(start = 0, len = 4)] a: u8,
#[field(start = 4, len = 4)] b: u8,
}
let f = Flags::builder().a(1).b(2).build()?;
let raw: u8 = f.raw(); // It's already the integer
let f2 = Flags::from_raw(raw); // Zero-cost conversion
This matters when your domain thinks in integers—database columns, wire protocols, hash keys—rather than structured data that happens to be packed.
This crate requires Rust 1.70 or later.
Licensed under either of
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.