Crate modular_bitfield

Source
Expand description

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, u64oru128` 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: Skipping Fields

It might make sense to only allow users to set or get information from a field or even to entirely disallow interaction with a bitfield. For this the #[skip] attribute can be used on a bitfield of a #[bitfield] annotated struct.

#[bitfield]
pub struct SomeBitsUndefined {
    #[skip(setters)]
    read_only: bool,
    #[skip(getters)]
    write_only: bool,
    #[skip]
    unused: B6,
}

It is possible to use #[skip(getters, setters)] or #[skip(getters)] followed by a #[skip(setters)] attribute applied on the same bitfield. The effects are the same. When skipping both, getters and setters, it is possible to completely avoid having to specify a name:

#[bitfield]
pub struct SomeBitsUndefined {
    #[skip] __: B2,
    is_activ: bool,
    #[skip] __: B2,
    is_received: bool,
    #[skip] __: B2,
}
§Example: Unfilled Bitfields

Sometimes it might be useful to not be required to construct a bitfield that defines all bits and therefore is required to have a bit width divisible by 8. In this case you can use the filled: bool parameter of the #[bitfield] macro in order to toggle this for your respective bitfield:

#[bitfield(filled = false)]
pub struct SomeBitsUndefined {
    is_compact: bool,
    is_secure: bool,
    pre_status: B3,
}

In the above example SomeBitsUndefined only defines the first 5 bits and leaves the rest 3 bits of its entire 8 bits undefined. The consequences are that its generated from_bytes method is fallible since it must guard against those undefined bits.

§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 the #[derive(BitfieldSpecifier)] attribute to the struct annotated with #[bitfield]:

#[bitfield(filled = false)]
#[derive(BitfieldSpecifier)]
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,
}

With the bits: int parameter of the #[bitfield] macro on the Header struct and the #[bits: int] attribute of the #[derive(BitfieldSpecifier)] on the Status enum we can have additional compile-time guarantees about the bit widths of the resulting entities:

#[derive(BitfieldSpecifier)]
#[bits = 2]
pub enum Status {
    Red, Green, Yellow, None,
}

#[bitfield(bits = 4)]
#[derive(BitfieldSpecifier)]
pub struct Header {
    is_compact: bool,
    is_secure: bool,
    #[bits = 2]
    pre_status: Status,
}

#[bitfield(bits = 16)]
pub struct PackedData {
    #[bits = 4]
    header: Header,
    body: B9,
    is_alive: bool,
    #[bits = 2]
    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,
}
SignatureDescription
fn new() -> SelfCreates a new instance of the bitfield with all bits initialized to 0.
fn from_bytes([u8; 1]) -> SelfCreates a new instance of the bitfield from the given raw bytes.
fn into_bytes(self) -> [u8; 1]Returns the underlying bytes of the bitfield.

And below the generated signatures for field a:

SignatureDescription
fn a() -> boolReturns 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) -> SelfSimilar 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§

  • Errors that can occure while operating on modular bitfields.
  • The prelude: use modular_bitfield::prelude::*;
  • The default set of predefined specifiers.

Traits§

  • Trait implemented by all bitfield specifiers.

Attribute Macros§

  • Applicable to structs to turn their fields into compact bitfields.

Derive Macros§