Crate scryer_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,
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: 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,
}
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 into_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 scryer_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 scryer_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§
- Derive macro for Rust
enums
to implementSpecifier
trait.