This crate provides macros that create bit fields and bit enums, which are useful in bit packing code (e.g. in drivers or networking code).
Some highlights:
A bit field is created similar to a regular Rust struct. Annotations define the layout of the structure. As an example, consider the following definition, which specifies a bit field:
#[bitfield(u32)]
struct GICD_TYPER {
#[bits(11..=15, r)]
lspi: u5,
#[bit(10, r)]
security_extn: bool,
#[bits(5..=7, r)]
cpu_number: u3,
#[bits(0..=4, r)]
itlines_number: u5,
}
How this works:
bits inside of the bitfield have to fit within 32 bits. Built-in Rust types (u8, u16, u32, u64, u128) as well as arbitrary-ints (u17, u48 etc) are supported.
Very often, fields aren't just numbers but really enums. This is supported by first defining a bitenum and then using that inside of a bitfield:
#[bitenum(u2, exhaustive = false)]
enum NonExhaustiveEnum {
Zero = 0b00,
One = 0b01,
Two = 0b10,
}
#[bitenum(u2, exhaustive = true)]
enum ExhaustiveEnum {
Zero = 0b00,
One = 0b01,
Two = 0b10,
Three = 0b11,
}
#[bitfield(u64, default = 0)]
struct BitfieldWithEnum {
#[bits(2..=3, rw)]
e2: Option<NonExhaustiveEnum>,
#[bits(0..=1, rw)]
e1: ExhaustiveEnum,
}
Sometimes, bits inside bitfields are repeated. To support this, this crate allows specifying bitwise arrays. For example, the following struct gives read/write access to each individual nibble (hex character) of a u64:
#[bitfield(u64, default = 0)]
struct Nibble64 {
#[bits(0..=3, rw)]
nibble: [u4; 16],
}
Arrays can also have a stride. This is useful in the case of multiple smaller values repeating. For example, the following definition provides access to each bit of each nibble:
#[bitfield(u64, default = 0)]
struct NibbleBits64 {
#[bit(0, rw, stride = 4)]
nibble_bit0: [bool; 16],
#[bit(1, rw, stride = 4)]
nibble_bit1: [bool; 16],
#[bit(2, rw, stride = 4)]
nibble_bit2: [bool; 16],
#[bit(3, rw, stride = 4)]
nibble_bit3: [bool; 16],
}
Bitfields can always be created through new_with_raw_value():
let a = NibbleBits64::new_with_raw_value(0x43218765);
However, pretty often a specific value can be considered the default (for example 0). This can be specified like this:
#[bitfield(u32, default = 0x1234)]
struct Bitfield1 {
#[bits(0..=3, rw)]
nibble: [u4; 4],
}
If a default value is specified, the bitfield can easily be created with this specific value:
let a = Bitfield1::Default;
const A: Bitfield1 = Bitfield1::DEFAULT;
Default values are used as-is, even if they affect bits that aren't defined within the bitfield.
It is possible to set all fields at once, like this:
const T: Test = Test::builder()
.with_baudrate(0x12)
.with_some_other_bits(u4::new(0x2))
.with_array([1, 2, 3, 4])
.build();
Using builder()
it is impossible to forget setting any fields. This is checked at compile time: If any field is not set, build()
can not be called.
At the moment, it is required to set all fields in the same order as they are specified. As Rust's const generics become more powerful, this restriction might be lifted.
For the builder()
to be available, the following has to be true:
Occasionally it can be useful for bitranges to not be contiguous. For example, RISC-V defines some immediates in a way that they have to be reassembled. This can be achieved like this:
#[bitfield(u32)]
struct SBFormat {
#[bits([8..=11, 25..=30, 7, 31], rw)]
imm_half: u12,
}
Arbitrary bit widths like u5 or u67 do not exist in Rust at the moment. Therefore, the following dependency is required:
arbitrary-int = "1.2.7"
Even though bitfields feel somewhat like structs, they are internally implemented as simple data types like u32. Therefore, they provide an immutable interface: Instead of changing the value of a field, any change operation will return a new bitfield with that field modified.
let a = NibbleBits64::new_with_raw_value(0x12345678_ABCDEFFF);
// Read a value
assert_eq!(u4::new(0xE), a.nibble(3));
// Change a value
let b = a.with_nibble(0, u4::new(0x3))
assert_eq!(0x12345678_ABCDEFF3, b.raw_value());