bitpiece
A Rust crate for effortlessly defining and manipulating bitfields with procedural macros.
bitpiece takes the complexity out of bit-level data manipulation. It provides a powerful #[bitpiece] macro that lets you define structs and enums as compact, typed bitfields, while automatically generating a safe, high-level API to interact with them. It's perfect for working with network protocols, hardware interfaces, or any scenario where data compactness is key.
Features
- Declarative & Simple: Define complex bitfield layouts using simple Rust structs and enums.
- Type-Safe API: The macro generates getters and setters for each field, so you work with
bool,u8,enumtypes, etc., not raw bit shifts and masks. - Flexible: Supports defining types which have exotic bit lengths, for example a 6-bit struct made of two 3-bit fields.
- Nestable: Compose complex bitfields by nesting
bitpiecetypes within each other. - Arbitrary-Width Integers: Use the built-in
B1-B64types (e.g.,B3,B7,B12) for unsigned integers with non-standard bit lengths, or theSB1-SB64types for signed integers. - Enums: Supports using enums as bitfields, with automatic validation of input for non-exhaustive enums.
- Compile-Time Validation: Optionally specify an expected bit length on your structs (e.g.,
#[bitpiece(32)]) to get a compile-time error if it doesn't match the sum of its fields. - Safe & Unsafe APIs: Provides both panicking (
from_bits) and fallible (try_from_bits) APIs for creating bitpieces from raw integer values. #![no_std]compatible.
Getting Started
First, add bitpiece to your Cargo.toml:
[]
= "0.1.0" # Use the latest version
Now, let's define a bitfield for a hypothetical network packet header.
use *;
// Define a 2-bit enum for the packet's priority.
// The macro automatically infers it needs 2 bits.
// Define the packet header structure.
// The macro calculates the total size (1 + 2 + 5 = 8 bits).
// The `(8)` is optional but validates the size at compile time.
More Examples
Nesting
You can easily build complex structures by nesting bitpiece types.
use *;
Non-Exhaustive Enums
By default, an enum's bit-length is determined by its largest variant. If you try to create an enum from an invalid integer value, it will panic.
Sometimes, however, an enum definition isn't complete, but you still want to handle known variants. For this, bitpiece generates a try_from_bits method.
use *;
// Bit length is inferred as 7 bits (from 120)
Explicit Bit-Length on Enums
You can give an enum a larger bit-width than it needs. This is useful when a protocol reserves a certain number of bits for an enum, even if not all values are currently used.
use *;
// This enum's highest value is 2, which only needs 2 bits.
// But we can force it to occupy a full byte.
Generated API
For a struct like MyPiece { field_a: bool, field_b: B3 }, the macro generates:
MyPiece::from_bits(u8) -> Self: Creates an instance from raw bits. Panics if any field gets an invalid value (e.g., for a non-exhaustive enum).MyPiece::try_from_bits(u8) -> Option<Self>: Safely creates an instance, returningNoneif any field would be invalid.my_piece.to_bits() -> u8: Returns the raw bits as the smallest possible integer storage type.MyPiece::from_fields(MyPieceFields) -> Self: Creates an instance from a struct containing all the fields.my_piece.to_fields() -> MyPieceFields: Deconstructs the instance into a struct of its fields.MyPiece::zeroes() -> Self: A constructor where all bits are 0.my_piece.field_a() -> bool: Getter forfield_a.my_piece.set_field_a(bool): Setter forfield_a.my_piece.field_b() -> B3: Getter forfield_b.my_piece.set_field_b(B3): Setter forfield_b.my_piece.field_a_mut() -> BitPieceMut: Advanced usage for mutable access, especially for nested pieces.my_piece.field_b_mut() -> BitPieceMut: Same as above, but for field_b
License: MIT