Macro mycelium_bitfield::bitfield

source ·
macro_rules! bitfield {
    (
        $(#[$($meta:meta)+])*
        $vis:vis struct $Name:ident<$T:ident> {
            $(
                $(#[$field_meta:meta])*
                $field_vis:vis const $Field:ident $(: $F:ty)? $( = $val:tt)?;
            )+
        }
    ) => { ... };
    (@field<$T:ident>, prev: $Prev:ident:
        $(#[$meta:meta])*
        $vis:vis const $Field:ident = ..;
    ) => { ... };
    (@field<$T:ident>, prev: $Prev:ident:
        $(#[$meta:meta])*
        $vis:vis const $Field:ident = $value:literal;
        $($rest:tt)*
    ) => { ... };
    (@field<$T:ident>, prev: $Prev:ident:
        $(#[$meta:meta])*
        $vis:vis const $Field:ident: $Val:ty;
        $($rest:tt)*
    ) => { ... };
    (@field<$T:ident>, prev: $Prev:ident: ) => { ... };
    (@field<$T:ident>:
        $(#[$meta:meta])*
        $vis:vis const $Field:ident = $value:literal;
        $($rest:tt)*
    ) => { ... };
    (@field<$T:ident>:
        $(#[$meta:meta])*
        $vis:vis const $Field:ident: $Val:ty;
        $($rest:tt)*
    ) => { ... };
    (@t usize, $V:ty, $F:ty) => { ... };
    (@t u128, $V:ty, $F:ty) => { ... };
    (@t u64, $V:ty, $F:ty) => { ... };
    (@t u32, $V:ty, $F:ty) => { ... };
    (@t u16, $V:ty, $F:ty) => { ... };
    (@t u8, $V:ty, $F:ty) => { ... };
    (@t $T:ty, $V:ty, $F:ty) => { ... };
}
Expand description

Generates a typed bitfield struct.

By default, the fmt::Debug, fmt::Display, fmt::Binary, Copy, and Clone traits are automatically derived for bitfields.

All bitfield types are #[repr(transparent)].

For a complete example of the methods generated by the bitfield! macro, see the example module’s ExampleBitfield type.

§Generated Implementations

The bitfield! macro generates a type with the following functions, where {int} is the integer type that represents the bitfield (one of u8, u16, u32, u64, u128, or usize):

  • const fn new() -> Self: Returns a new instance of the bitfield type with all bits zeroed.
  • const fn from_bits(bits: {int}) -> Self: Converts an {int} into an instance of the bitfield type.
  • const fn bits(self) -> {int}: Returns this bitfield’s bits as a raw integer value.
  • fn with<U>(self, packer: Self::Packer<U>, value: U) -> Self: Given one of this type’s generated packing specs for a U-typed value, and a U-typed value, returns a new instance of Self with the bit representation of value packed into the range represented by packer.
  • fn set<U>(&mut self, packer: Self::Packer<U>, value: U) -> &mut Self: Similar to with, except self is mutated in place, rather than returning a new nstance of Self
  • fn get<U>(&self, packer: Self::Packer<U>) -> U: Given one of this type’s generated packing specs for a U-typed value, unpacks the bit range represented by that value as a U and returns it. This method panics if the requested bit range does not contain a valid bit pattern for a U-typed value, as determined by U’s implementation of the FromBits trait.
  • fn try_get<U>(&self, packer: Self::Packer<U>) -> Result<U, <U as FromBits>::Error>: Like get, but returns a Result instead of panicking.
  • fn assert_valid(): Asserts that the generated bitfield type is valid. This is primarily intended to be used in tests; the macro cannot generate tests for a bitfield type on its own, so a test that simply calls assert_valid can be added to check the bitfield type’s validity.
  • fn display_ascii(&self) -> impl core::fmt::Display: Returns a fmt::Display implementation that formats the bitfield in a multi-line format, using only ASCII characters. See here for examples of this format.
  • fn display_unicode(&self) -> impl core::fmt::Display: Returns a fmt::Display implementation that formats the bitfield in a multi-line format, always using Unicode box-drawing characters. See here for examples of this format.

The visibility of these methods depends on the visibility of the bitfield struct — if the struct is defined as pub(crate) struct MyBitfield<u16> { ... }, then these functions will all be pub(crate) as well.

If a bitfield type is defined with one visibility, but particular subfields of that bitfield should not be public, the individual fields may also have visibility specifiers. For example, if the bitfield struct MyBitfield is pub, but the subfield named PRIVATE_SUBFIELD is pub(crate), then my_bitfield.get(MyBitfield::PRIVATE_SUBRANGE) can only be called inside the crate defining the type, because the PRIVATE_SUBRANGE constant is not publicly visible.

In addition to the inherent methods discussed above, the following trait implementations are always generated:

  • fmt::Debug: The Debug implementation prints the bitfield as a “struct”, with a “field” for each packing spec in the bitfield. If any of the bitfield’s packing specs pack typed values, that type’s fmt::Debug implementation is used rather than printing the value as an integer.
  • fmt::Binary: Prints the raw bits of this bitfield as a binary number.
  • fmt::UpperHex and fmt::LowerHex: Prints the raw bits of this bitfield in hexadecimal.
  • fmt::Display: Pretty-prints the bitfield in a very nice-looking multi-line format which I’m rather proud of. See here for examples of this format.
  • Copy: Behaves identically as the Copy implementation for the underlying integer type.
  • Clone: Behaves identically as the Clone implementation for the underlying integer type.
  • From<{int}> for Self: Converts a raw integer value into an instance of the bitfield type. This is equivalent to calling the bitfield type’s from_bits function.
  • From<Self> for {int}: Converts an instance of the bitfield type into a raw integer value. This is equivalent to calling the bitfield type’s bits method.

Additional traits may be derived for the bitfield type, such as PartialEq, Eq, and Default. These traits are not automatically derived, as custom implementations may also be desired, depending on the use-case. For example, the Default value for a bitfield may not be all zeroes.

§Examples

Basic usage:

mycelium_bitfield::bitfield! {
    /// Bitfield types can have doc comments.
    #[derive(Eq, PartialEq)] // ...and attributes
    pub struct MyBitfield<u16> {
        // Generates a packing spec named `HELLO` for the first 6
        // least-significant bits.
        pub const HELLO = 6;
        // Fields with names starting with `_` can be used to mark bits as
        // reserved.
        const _RESERVED = 4;
        // Generates a packing spec named `WORLD` for the next 3 bits.
        pub const WORLD = 3;
    }
}

// Bitfield types can be cheaply constructed from a raw numeric
// representation:
let bitfield = MyBitfield::from_bits(0b10100_0011_0101);

// `get` methods can be used to unpack fields from a bitfield type:
assert_eq!(bitfield.get(MyBitfield::HELLO), 0b11_0101);
assert_eq!(bitfield.get(MyBitfield::WORLD), 0b0101);

// `with` methods can be used to pack bits into a bitfield type by
// value:
let bitfield2 = MyBitfield::new()
    .with(MyBitfield::HELLO, 0b11_0101)
    .with(MyBitfield::WORLD, 0b0101);

assert_eq!(bitfield, bitfield2);

// `set` methods can be used to mutate a bitfield type in place:
let mut bitfield3 = MyBitfield::new();

bitfield3
    .set(MyBitfield::HELLO, 0b011_0101)
    .set(MyBitfield::WORLD, 0b0101);

assert_eq!(bitfield, bitfield3);

Bitfields may also contain typed values, as long as those values implement the FromBits trait. FromBits may be manually implemented, or generated automatically for enum types using the enum_from_bits! macro:

use mycelium_bitfield::{bitfield, enum_from_bits, FromBits};

// An enum type can implement the `FromBits` trait if it has a
// `#[repr(uN)]` attribute.
#[repr(u8)]
#[derive(Debug, Eq, PartialEq)]
enum MyEnum {
    Foo = 0b00,
    Bar = 0b01,
    Baz = 0b10,
}

impl FromBits<u32> for MyEnum {
    // Two bits can represent all possible `MyEnum` values.
    const BITS: u32 = 2;
    type Error = &'static str;

    fn try_from_bits(bits: u32) -> Result<Self, Self::Error> {
        match bits as u8 {
            bits if bits == Self::Foo as u8 => Ok(Self::Foo),
            bits if bits == Self::Bar as u8 => Ok(Self::Bar),
            bits if bits == Self::Baz as u8 => Ok(Self::Baz),
            _ => Err("expected one of 0b00, 0b01, or 0b10"),
        }
    }

    fn into_bits(self) -> u32 {
        self as u8 as u32
    }
}

// Alternatively, the `enum_from_bits!` macro can be used to
// automatically generate a `FromBits` implementation for an
// enum type.
//
// The macro will generate very similar code to the example
// manual implementation above.
enum_from_bits! {
    #[derive(Debug, Eq, PartialEq)]
    pub enum MyGeneratedEnum<u8> {
        /// Isn't this cool?
        Wow = 0b1001,
        /// It sure is! :D
        Whoa = 0b0110,
    }
}

bitfield! {
    pub struct TypedBitfield<u32> {
        /// Use the first two bits to represent a typed `MyEnum` value.
        const ENUM_VALUE: MyEnum;

        /// Typed values and untyped raw bit fields can be used in the
        /// same bitfield type.
        pub const SOME_BITS = 6;

        /// The `FromBits` trait is also implemented for `bool`, which
        /// can be used to implement bitflags.
        pub const FLAG_1: bool;
        pub const FLAG_2: bool;

        /// `FromBits` is also implemented by (signed and unsigned) integer
        /// types. This will allow the next 8 bits to be treated as a `u8`.
        pub const A_BYTE: u8;

        /// We can also use the automatically generated enum:
        pub const OTHER_ENUM: MyGeneratedEnum;
    }
}

// Unpacking a typed value with `get` will return that value, or panic if
// the bit pattern is invalid:
let my_bitfield = TypedBitfield::from_bits(0b0010_0100_0011_0101_1001_1110);

assert_eq!(my_bitfield.get(TypedBitfield::ENUM_VALUE), MyEnum::Baz);
assert_eq!(my_bitfield.get(TypedBitfield::FLAG_1), true);
assert_eq!(my_bitfield.get(TypedBitfield::FLAG_2), false);
assert_eq!(my_bitfield.get(TypedBitfield::OTHER_ENUM), MyGeneratedEnum::Wow);

// The `try_get` method will return an error rather than panicking if an
// invalid bit pattern is encountered:

let invalid = TypedBitfield::from_bits(0b0011);

// There is no `MyEnum` variant for 0b11.
assert!(invalid.try_get(TypedBitfield::ENUM_VALUE).is_err());

Packing specs from one bitfield type may not be used with a different bitfield type’s get, set, or with methods. For example, the following is a type error:

use mycelium_bitfield::bitfield;

bitfield! {
    struct Bitfield1<u8> {
        pub const FOO: bool;
        pub const BAR: bool;
        pub const BAZ = 6;
    }
}

bitfield! {
    struct Bitfield2<u8> {
        pub const ALICE = 2;
        pub const BOB = 4;
        pub const CHARLIE = 2;
    }
}


// This is a *type error*, because `Bitfield2`'s field `ALICE` cannot be
// used with a `Bitfield2` value:
let bits = Bitfield1::new().with(Bitfield2::ALICE, 0b11);

§Example Display Output

Bitfields will automatically generate a pretty, multi-line fmt::Display implementation. The default fmt::Display specifier uses only ASCII characters, but when Unicode box-drawing characters are also available, the alternate ({:#}) fmt::Display specifier may be used to select a Display implementation that uses those characters, and is (in my opinion) even prettier than the default.

For example:

// Create an example bitfield.
let my_bitfield = TypedBitfield::from_bits(0b0011_0101_1001_1110);

// The default `Display` implementation uses only ASCII characters:
let formatted_ascii = format!("{my_bitfield}");
let expected = r#"
00000000000000000011010110011110
..............................10 ENUM_VALUE: Baz
........................100111.. SOME_BITS: 39
.......................1........ FLAG_1: true
......................0......... FLAG_2: false
..............00001101.......... A_BYTE: 13
"#.trim_start();
assert_eq!(formatted_ascii, expected);

// The alternate `Display` format uses Unicode box-drawing characters,
// and looks even nicer:
let formatted_unicode = format!("{my_bitfield:#}");
let expected = r#"
00000000000000000011010110011110
              └┬─────┘││└┬───┘└┤
               │      ││ │     └ ENUM_VALUE: Baz (10)
               │      ││ └────── SOME_BITS: 39 (100111)
               │      │└─────────── FLAG_1: true (1)
               │      └──────────── FLAG_2: false (0)
               └─────────────────── A_BYTE: 13 (00001101)
"#.trim_start();
assert_eq!(formatted_unicode, expected);

For situations where the use of ASCII or Unicode formats is always desired regardless of the behavior of an upstream formatter (e.g., when returning a fmt::Display value that doesn’t know what format specifier will be used), bitfield types also generate display_ascii() and display_unicode() methods. These methods return impl fmt::Display values that always select either the ASCII or Unicode Display implementations explicitly, regardless of whether or not the alternate formatting specifier is used.