mycelium-bitfield

🍄 bitfield utilities, courtesy of [Mycelium].

crates.io Documentation Documentation (HEAD) MIT licensed Test Status Sponsor @hawkw on GitHub Sponsors

what is it?

This library provides utilities for defining structured bitfields in Rust. It consists of a set of types for defining ranges that can be packed and unpacked from an integer value, and a [bitfield! macro][bitfield!] for generating bitfield types automatically using the packing types. These components are modular: it's possible to use the packing spec types to hand-write all of the code that the bitfield! macro would generate.

This crate was originally implemented for usage in the Mycelium operating system, although it is usable in other projects and does not depend on any Mycelium-specific libraries.

comparison with other crates

There are several other crates implementing bitfields or bitfield-related utilities in Rust. These crates offer different, but sometimes overlapping, functionality relative to mycelium-bitfield. In particular, the most directly comparable crates that I'm currently aware of are the [modular-bitfield] and [bitflags] libraries.

Note This crate exists primarily because I thought it would be fun to write my own bitfield crate, not because the existing libraries were deficient. It is possible that I have a somewhat perverse conception of "fun"...

The [modular-bitfield] crate, in particular, can do most of the same things as mycelium-bitfield. However, there are some differences between the two libraries which may be interesting to consider.

usage

This crate's API consists of three primary components, the packing spec types, the bitfield! macro, and the FromBits trait.

packing spec types

The pack module defines a set of types that can be used to pack and unpack ranges from integer values of various sizes, such as [Pack64] for packing and unpacking a range from a u64 value.

These packing spec types have const fn constructors that allow them to be defined in relationship with each other. For example:

```rust use mycelium_bitfield::Pack64;

// Defines a packing spec for the least-significant 12 bits of a 64-bit value. const LOW: Pack64 = Pack64::least_significant(12); // Defines a packing spec for the next 8 more-significant bits after LOW. const MID: Pack64 = LOW.next(8); // Defines a packing spec for the next 4 more-significant bits after MID. const HIGH: Pack64 = MID.next(4);

// Wrap an integer value to pack it using method calls. let coffee = Pack64::pack_in(0) // pack the 12 bits of 0xfee at the range specified by LOW. .pack(0xfee, &LOW) // pack the 4 bits 0xc at the range specified by HIGH. .pack(0xc, &HIGH) // pack 0xf in the 8 bits specified by MID. .pack(0xf, &MID) // unwrap the packing value back into a u64. .bits();

assert_eq!(coffee, 0xc0ffee); // i want c0ffee ```

A majority of the functions in the pack module are const fns, allowing the use of packing specs in const contexts.

See the module-level docs for pack for details.

bitfield! macro

The [bitfield!][bitfield!] macro allows defining a structured bitfield type declaratively. The macro will generate code that uses the pack module's packing spec APIs to represent a bitfield type.

For example: ``rust mycelium_bitfield::bitfield! { /// Bitfield types can have doc comments. #[derive(Eq, PartialEq)] // ...and attributes pub struct MyBitfield<u16> { /// Generates a packing spec namedHELLO` 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;

    /// A boolean value will generate a packing spec for a single bit.
    pub const FLAG: bool;
}

}

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

// get methods can be used to unpack fields from a bitfield type: asserteq!(bitfield.get(MyBitfield::HELLO), 0b110101); 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); ```

See the [bitfield!] macro's documentation for details on the macro's usage and the code it generates.

FromBits trait

The [FromBits] trait can be implemented for user-defined types which can be used as subfields of a [bitfield!]-generated structured bitfield type. This trait may be manually implemented for any user-defined type that has a defined bit representation, or generated automatically for enum types using the [enum_from_bits!] macro.

For example: ```rust use myceliumbitfield::{bitfield, enumfrom_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 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: enumfrombits! { #[derive(Debug, Eq, PartialEq)] pub enum MyGeneratedEnum { /// Isn't this cool? Wow = 0b1001, /// It sure is! :D Whoa = 0b0110, } }

bitfield! { pub struct TypedBitfield { /// 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 mybitfield = TypedBitfield::frombits(0b00100100001101011001_1110);

asserteq!(mybitfield.get(TypedBitfield::ENUMVALUE), MyEnum::Baz); asserteq!(mybitfield.get(TypedBitfield::FLAG1), true); asserteq!(mybitfield.get(TypedBitfield::FLAG2), false); asserteq!(mybitfield.get(TypedBitfield::OTHERENUM), 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.tryget(TypedBitfield::ENUMVALUE).is_err()); ```

See the [FromBits trait documentation][FromBits] for details on implementing [FromBits] for user-defined types.