🍄 bitfield utilities, courtesy of [Mycelium].
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.
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 asmycelium-bitfield
. However, there are some differences between the two libraries which may be interesting to consider.
[bitflags
]: The [bitflags
] crate provides a declarative macro for
generating a structured type representing a set of bitflags.
The critical difference between [bitflags
]' bitflags!
macro and mycelium-bitfield
's [bitfield!
] macro is that the [bitflags
]
crate only implements bitflags, not bitfields. It is not possible to
define multi-bit structured ranges using [bitflags
]; only single-bit flags
can be set and unset.
However, the [bitflags
] crate is widely used, has been around for a long
time, and is relatively simple and lightweight. If all you need is a set of
boolean, single-bit flags, it's a very solid choice. But, if you need
mycelium-bitfield
's additional functionality for working with multi-bit
ranges, note that it can also do most most of what the bitflags
crate can
do.
[modular-bitfield
]: The [modular-bitfield
] crate provides a procedural
macro for generating typed structured bitfields.
The functionality implemented by [modular-bitfield
] is broadly very similar
to mycelium-bitfield
— the two libraries can do most of the same
things.
The primary difference is that [modular-bitfield
] is implemented
using a procedural macro attribute, while mycelium-bitfield
's
[bitfield!
macro][bitfield!
] is a declarative macro. In my opinion,
this isn't a reason to prefer mycelium-bitfield
over [modular-bitfield
] in
most use cases. I decided to try to write the whole thing using a declarative
macro because I thought it would be a fun challenge, not because it's
better (in fact, it would probably have been much easier to implement
the bitfield type generation using a procedural macro). However, users who
need to reduce or avoid procedural macros for some reason may want to consider
choosing mycelium-bitfield
for that reason.
The other primary difference between mycelium-bitfield
and
[modular-bitfield
] is that mycelium-bitfield
also provides the
pack
module with packing spec types. These types can be used to
build bitfield types "by hand", in cases where different behavior from the
macro-generated code is needed. [modular-bitfield
] only provides a
procedural macro, and does not have an equivalent to this lower-level
interface.
On the other hand, [modular-bitfield
] provides
nicer validation that a typed value used as part of a
bitfield actually fits in that bitfield. mycelium-bitfield
cannot currently
do this kind of compile-time checking, and relies on implementations of the
[FromBits
trait][FromBits
] for user-provided types being correct.
This crate's API consists of three primary components, the packing spec
types, the bitfield! macro
, and the
FromBits
trait.
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 fn
s, allowing
the use of packing specs in const contexts.
See the module-level docs for pack
for details.
bitfield!
macroThe [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 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;
/// 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
traitThe [FromBits
] trait can be implemented for user-defined types which can be
used as subfields of a [bitfield!
]-generated structured bitfield type.
For example: ```rust use mycelium_bitfield::{bitfield, FromBits};
// An enum type can implement the FromBits
trait if it has a
// #[repr(uN)]
attribute.
enum MyEnum { Foo = 0b00, Bar = 0b01, Baz = 0b10, }
impl FromBitsMyEnum
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
}
}
bitfield! {
pub struct TypedBitfieldMyEnum
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;
}
}
// Unpacking a typed value with get
will return that value, or panic if
// the bit pattern is invalid:
let mybitfield = TypedBitfield::frombits(0b001101011001_1110);
asserteq!(mybitfield.get(TypedBitfield::ENUMVALUE), MyEnum::Baz); asserteq!(mybitfield.get(TypedBitfield::FLAG1), true); asserteq!(mybitfield.get(TypedBitfield::FLAG_2), false);
// 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.