Crate mvbitfield

source ·
Expand description

mvbitfield generates types to work with bit-aligned fields.

Bitfield structs serve roughly the same use cases as C/C++ structs with bit-field members and are:

  • Endian-insensitive, packing fields within an integer rather than across bytes or array elements.
  • Flexible and type-safe with optional user-defined field accessor types.
  • Suitable for FFI and memory-mapped I/O with care, as always.

Bitfield enums are unit-only Rust enums with a declared bit width that provide safe zero-cost conversions to and from an integer type and can be used as accessors in a bitfield struct.

Demo

// Recommended, but not required. The mvbitfield prelude includes the bitint
// prelude.
use mvbitfield::prelude::*;

bitfield! {
    #[lsb_first]               // Field packing order.
    #[derive(PartialOrd, Ord)] // Other attributes are passed through.
    pub struct MyBitfieldStruct: 32 {
        // The lowest three bits with public bitint::U3 accessors.
        pub some_number: 3,

        // The next eight bits with public bitint::U8 accessors.
        pub another_number: 8,

        // No accessors for field names starting with _.
        _padding: 2,

        // Private bitint::U11 accessors.
        internal_number: 11,

        // Skip unused bits, in this case five bits.
        ..,

        // The two next-to-most significant bits with public MyBitfieldEnum
        // accessors.
        pub an_enum: 2 as MyBitfieldEnum,

        // Private bool accessors.
        high_bit_flag: 1 as bool,
    }

    pub enum MyBitfieldEnum: 2 {
        // Declare up to 2^width unit variants with optional explicit
        // discriminants.
        Three = 3,
        Zero = 0,
        One,

        // Generates `Unused2` to complete the enum.
        ..
    }
}

#[bitint_literals]
fn main() {
    // Use generated with_* methods to build bitfield structs.
    let x = MyBitfieldStruct::zero()
        .with_some_number(6_U3)
        .with_another_number(0xa5_U8)
        .with_internal_number(1025_U11)
        .with_an_enum(MyBitfieldEnum::One)
        .with_high_bit_flag(true);

    // Default accessors return bitints.
    assert_eq!(x.some_number(), 6_U3);
    assert_eq!(x.some_number().to_primitive(), 6);
    assert_eq!(x.another_number(), 0xa5_U8);
    assert_eq!(x.another_number().to_primitive(), 0xa5);
    assert_eq!(x.internal_number(), 1025_U11);
    assert_eq!(x.internal_number().to_primitive(), 1025);

    // Custom accessors return the chosen type, which must have Into
    // conversions to and from the default accessor bitint.
    assert_eq!(x.an_enum(), MyBitfieldEnum::One);
    assert_eq!(x.high_bit_flag(), true);

    // Zero-cost conversions to and from bitints and to primitive.
    // For bitfield structs:
    assert_eq!(x.to_bitint(), 0b1_01_00000_10000000001_00_10100101_110_U32);
    assert_eq!(x.to_primitive(), 0b1_01_00000_10000000001_00_10100101_110);
    assert_eq!(x, MyBitfieldStruct::from_bitint(0xa080252e_U32));
    // For bitfield enums:
    assert_eq!(MyBitfieldEnum::One.to_bitint(), 1_U2);
    assert_eq!(MyBitfieldEnum::One.to_primitive(), 1);
    assert_eq!(MyBitfieldEnum::One, MyBitfieldEnum::from_bitint(1_U2));

    // Zero-cost conversion from primitive, only for primitive-sized
    // bitfield structs and enums.
    assert_eq!(x, MyBitfieldStruct::from_primitive(0xa080252e));
    bitfield! { enum MyEightBitEnum: 8 { X = 192, .. } }
    assert_eq!(MyEightBitEnum::X, MyEightBitEnum::from_primitive(192));

    // Bitfield enums optionally generate placeholder variants for unused
    // discriminants with `..`. The name is always "Unused" followed by the
    // discriminant value in base 10.
    assert_eq!(MyBitfieldEnum::Unused2.to_bitint(), 2_U2);
    assert_eq!(MyBitfieldEnum::Unused2, MyBitfieldEnum::from_bitint(2_U2));
}

Associated types

Bitfield types have two associated types: a bitint type and a primitive type. The bitint type is the bitfield type’s canonical integer representation and is one of the 128 unsigned types from the bitint crate. The primitive type is the bitint type’s primitive type.

The Bitfield::Bitint, Bitfield::Primitive, and UBitint::Primitive associated types model these relationships.

Bitfield structs

Bitfield structs are declared with a sequence of fields, but unlike regular Rust structs those fields are not directly exposed. Instead, they are packed into an integer and are only available by value through accessor methods that perform the necessary shifting and masking operations.

Examples

See BitfieldStruct24 and BitfieldStruct32 for bitfield! invocations and the resulting generated types.

Bitfield struct packing

Fields occupy contiguous ranges of bits and are tightly packed in declaration order. Each bit must be covered by precisely one field. The .. shorthand for a flexible field may be convenient to cover unused bits at either end or in the middle.

Packing begins with the first declared field at either the least or most significant bit, depending on the packing order attribute. If there is only one field, it must cover every bit and the packing order attribute is optional.

Bitfield struct layout

A bitfield struct has the same layout as its bitint type. Bitfield structs of widths 8, 16, 32, 64, or 128 are particularly well suited for memory-mapped I/O and foreign function interface bindings because their bitint types have no forbidden bit patterns. Bitfield structs of other widths require more care in unsafe contexts because their bitint types have unused upper bits that must remain clear.

Bitfield struct trait implementations

Bitfield structs implement the Bitfield trait and its requirements:

You are free to provide more trait impls alongside the bitfield! invocation, as with any other type. The bitfield! macro preserves attributes it doesn’t recognize and applies them to the generated type, so you can request additional derives as well.

bitfield! {
    #[derive(PartialOrd, Ord)]
    #[msb_first]
    pub struct MyStruct: 12 {
        pub high_bit: 1 as bool,
        ..
    }
}

trait MyOtherTrait {
    fn get_five() -> i32;
}

impl MyOtherTrait for MyStruct {
    fn get_five() -> i32 { 5 }
}

assert_eq!(MyStruct::get_five(), 5);
assert!(MyStruct::zero() < MyStruct::zero().with_high_bit(true));

Bitfield struct constructors and conversions

Bitfield structs provide all of the Bitfield trait methods and conversions to and from the bitint and primitive type as const inherent methods.

impl MyBitfieldStruct {
    pub const ZERO: Self;

    pub const fn zero() -> Self;

    pub const fn new(value: Self::Primitive) -> Option<Self>;

    pub const fn new_masked(value: Self::Primitive) -> Self;

    pub const unsafe fn new_unchecked(value: Self::Primitive) -> Self;

    pub const fn from_bitint(value: Self::Bitint) -> Self;

    // Only for primitive widths.
    pub const fn from_primitive(value: Self::Primitive) -> Self;

    pub const fn to_bitint(self) -> Self::Bitint;

    pub const fn to_primitive(self) -> Self::Primitive;
}

See the rustdoc on any generated bitfield struct type for details on behavior, invariants, cost, and safety.

Field accessors

impl MyBitfieldStruct {
    pub fn my_field(self) -> T;

    pub fn with_my_field(self, value: T) -> Self;

    pub fn map_my_field(self, f: impl FnOnce(T) -> T) -> Self;

    pub fn set_my_field(&mut self, value: T);

    pub fn replace_my_field(&mut self, value: T) -> T;

    pub fn update_my_field(&mut self, f: impl FnOnce(T) -> T) -> T;
}

where my_field is the field name and T is the field accessor type.

Note that field accessor methods are not const because they rely on Into conversions (plus FnOnce invocations for map and update), which cannot be const as of Rust 1.69.

Bitfield enums

Bitfield enums are unit-only/fieldless Rust enums that have a declared bit width and corresponding bitint type. A bitfield enum with width n has precisely 2ⁿ variants, one for each of the bitint type’s valid primitive values. This allows for sound zero-cost conversions to and from the bitint type.

A maximum width is currently enforced at 10 bits to keep compile times and memory usage reasonable.

Examples

See BitfieldEnum1 and BitfieldEnum3 for practical bitfield! invocations and the resulting generated types. See BitfieldEnum8 for a perhaps impractically large bitfield enum that has primitive width, allowing an additional zero-cost from_primitive method and From<u8> impl in place of TryFrom<u8>.

Bitfield enum layout

A bitfield enum has the same layout as its bitint type.

Bitfield enum trait implementations

Like bitfield structs, bitfield enums implement the Bitfield trait and its requirements. Attributes are passed through to the generated type, permitting doc comments and additional derives.

Bitfield enum constructors and conversions

Bitfield enums provide all of the Bitfield trait methods and conversions to and from the bitint and primitive type as const inherent methods.

impl MyBitfieldEnum {
    pub const ZERO: Self;

    pub const fn zero() -> Self;

    pub const fn new(value: Self::Primitive) -> Option<Self>;

    pub const fn new_masked(value: Self::Primitive) -> Self;

    pub const unsafe fn new_unchecked(value: Self::Primitive) -> Self;

    pub const fn from_bitint(value: Self::Bitint) -> Self;

    // Only for primitive widths.
    pub const fn from_primitive(value: u8) -> Self;

    pub const fn to_bitint(self) -> Self::Bitint;

    pub const fn to_primitive(self) -> Self::Primitive;
}

See the rustdoc on any generated bitfield enum type for details on behavior, invariants, cost, and safety.

Declaration syntax

A detailed reference is provided with the bitfield! macro.

Re-exports

Modules

Macros

Traits