[−][src]Crate modular_bitfield
Provides macros to support bitfield structs allowing for modular use of bit-enums.
The mainly provided macros are #[bitfield]
for structs and
#[derive(BitfieldSpecifier)]
for enums that shall be usable
within bitfield structs.
There are preset bitfield specifiers such as B1
, B2
,..,B64
that allow for easy bitfield usage in structs very similar to how
they work in C or C++.
- Performance of the macro generated code is as fast as its hand-written alternative.
- Compile-time checks allow for safe usage of bitfield structs and enums.
Usage
Annotate a Rust struct with the #[bitfield]
attribute in order to convert it into a bitfield.
The B1
, B2
, ... B128
prelude types can be used as primitives to declare the number of bits per field.
#[bitfield] pub struct PackedData { header: B4, body: B9, is_alive: B1, status: B2, }
This produces a new
constructor as well as a variety of getters and setters that
allows to interact with the bitfield in a safe fashion:
Example: Constructors
let data = PackedData::new() .with_header(1) .with_body(2) .with_is_alive(0) .with_status(3); assert_eq!(data.header(), 1); assert_eq!(data.body(), 2); assert_eq!(data.is_alive(), 0); assert_eq!(data.status(), 3);
Example: Primitive Types
Any type that implements the Specifier
trait can be used as a bitfield field.
Besides the already mentioned B1
, .. B128
also the bool
, u8,
u16, u32,
u64or
u128` primitive types can be used from prelude.
We can use this knowledge to encode our is_alive
as bool
type instead of B1
:
#[bitfield] pub struct PackedData { header: B4, body: B9, is_alive: bool, status: B2, } let mut data = PackedData::new() .with_is_alive(true); assert!(data.is_alive()); data.set_is_alive(false); assert!(!data.is_alive());
Example: Enum Specifiers
It is possible to derive the Specifier
trait for enum
types very easily to make
them also usable as a field within a bitfield type:
#[derive(BitfieldSpecifier)] pub enum Status { Red, Green, Yellow, None, } #[bitfield] pub struct PackedData { header: B4, body: B9, is_alive: bool, status: Status, }
Example: Extra Safety Guard
In order to make sure that our Status
enum still requires exatly 2 bit we can add
#[bits = 2]
to its field:
#[bitfield] pub struct PackedData { header: B4, body: B9, is_alive: bool, #[bits = 2] status: Status, }
Setting and getting our new status
field is naturally as follows:
let mut data = PackedData::new() .with_status(Status::Green); assert_eq!(data.status(), Status::Green); data.set_status(Status::Red); assert_eq!(data.status(), Status::Red);
Example: Recursive Bitfields
It is possible to use #[bitfield]
structs as fields of #[bitfield]
structs.
This is generally useful if there are some common fields for multiple bitfields
and is achieved by adding specifier = true
to the parameters of the #[bitfield]
attribute:
#[bitfield(specifier = true)] pub struct Header { is_compact: bool, is_secure: bool, pre_status: Status, } #[bitfield] pub struct PackedData { header: Header, body: B9, is_alive: bool, status: Status, }
Example: Advanced Enum Specifiers
For our Status
enum we actually just need 3 status variants: Green
, Yellow
and Red
.
We introduced the None
status variants because Specifier
enums by default are required
to have a number of variants that is a power of two. We can ship around this by specifying
#[bits = 2]
on the top and get rid of our placeholder None
variant while maintaining
the invariant of it requiring 2 bits:
#[derive(BitfieldSpecifier)] #[bits = 2] pub enum Status { Red, Green, Yellow, }
However, having such enums now yields the possibility that a bitfield might contain invalid bit
patterns for such fields. We can safely access those fields with protected getters. For the sake
of demonstration we will use the generated from_bytes
constructor with which we can easily
construct bitfields that may contain invalid bit patterns:
let mut data = PackedData::from_bytes([0b0000_0000, 0b1100_0000]); // The 2 status field bits are invalid -----^^ // as Red = 0x00, Green = 0x01 and Yellow = 0x10 assert_eq!(data.status_or_err(), Err(InvalidBitPattern { invalid_bytes: 0b11 })); data.set_status(Status::Green); assert_eq!(data.status_or_err(), Ok(Status::Green));
Generated Implementations
For the example #[bitfield]
struct the following implementations are going to be generated:
#[bitfield] pub struct Example { a: bool, b: B7, }
Signature | Description |
---|---|
fn new() -> Self | Creates a new instance of the bitfield with all bits initialized to 0. |
fn from_bytes([u8; 1]) -> Self | Creates a new instance of the bitfield from the given raw bytes. |
fn as_bytes(&self) -> &[u8; 1] | Returns the underlying bytes of the bitfield. |
And below the generated signatures for field a
:
Signature | Description |
---|---|
fn a() -> bool | Returns the value of a or panics if invalid. |
fn a_or_err() -> Result<bool, InvalidBitPattern<u8>> | Returns the value of a of an error providing information about the invalid bits. |
fn set_a(&mut self, new_value: bool) | Sets a to the new value or panics if new_value contains invalid bits. |
fn set_a_checked(&mut self, new_value: bool) -> Result<(), OutOfBounds> | Sets a to the new value of returns an out of bounds error. |
fn with_a(self, new_value: bool) -> Self | Similar to set_a but useful for method chaining. |
fn with_a_checked(self, new_value: bool) -> Result<Self, OutOfBounds> | Similar to set_a_checked but useful for method chaining. |
Generated Structure
From David Tolnay's procedural macro workshop:
The macro conceptualizes given structs as a sequence of bits 0..N. The bits are grouped into fields in the order specified by the struct written by the user.
The #[bitfield]
attribute rewrites the caller's struct into a private byte array representation
with public getter and setter methods for each field.
The total number of bits N is required to be a multiple of 8: This is checked at compile time.
Example
The following invocation builds a struct with a total size of 32 bits or 4 bytes.
It places field a
in the least significant bit of the first byte,
field b
in the next three least significant bits,
field c
in the remaining four most significant bits of the first byte,
and field d
spanning the next three bytes.
use modular_bitfield::prelude::*; #[bitfield] pub struct MyFourBytes { a: B1, b: B3, c: B4, d: B24, }
least significant bit of third byte
┊ most significant
┊ ┊
┊ ┊
║ first byte ║ second byte ║ third byte ║ fourth byte ║
╟───────────────╫───────────────╫───────────────╫───────────────╢
║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║
╟─╫─────╫───────╫───────────────────────────────────────────────╢
║a║ b ║ c ║ d ║
┊ ┊
┊ ┊
least significant bit of d most significant
Modules
error | Errors that can occure while operating on modular bitfields. |
prelude | The prelude: |
specifiers | The default set of predefined specifiers. |
Traits
Specifier | Trait implemented by all bitfield specifiers. |
Attribute Macros
bitfield | Attribute applicable to structs that turns them into bitfield structs. |
Derive Macros
BitfieldSpecifier | Derive macro for enums. |