# mycelium-bitfield
🍄 bitfield utilities, courtesy of [Mycelium].
[![crates.io][crates-badge]][crates-url]
[![Documentation][docs-badge]][docs-url]
[![Documentation (HEAD)][docs-main-badge]][docs-main-url]
[![MIT licensed][mit-badge]][mit-url]
[![Test Status][tests-badge]][tests-url]
[![Sponsor @hawkw on GitHub Sponsors][sponsor-badge]][sponsor-url]
[crates-badge]: https://img.shields.io/crates/v/mycelium-bitfield.svg
[crates-url]: https://crates.io/crates/mycelium-bitfield
[docs-badge]: https://docs.rs/mycelium-bitfield/badge.svg
[docs-url]: https://docs.rs/mycelium-bitfield
[docs-main-badge]: https://img.shields.io/netlify/3ec00bb5-251a-4f83-ac7f-3799d95db0e6?label=docs%20%28main%20branch%29
[docs-main-url]: https://mycelium.elizas.website/mycelium-bitfield
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: ../LICENSE
[tests-badge]: https://github.com/hawkw/mycelium/actions/workflows/ci.yml/badge.svg?branch=main
[tests-url]: https://github.com/hawkw/mycelium/actions/workflows/ci.yml
[sponsor-badge]: https://img.shields.io/badge/sponsor-%F0%9F%A4%8D-ff69b4
[sponsor-url]: https://github.com/sponsors/hawkw
## what is it?
This library provides utilities for defining structured bitfields in Rust. It
consists of [a set of types][pack] 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][Mycelium], 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.
* **[`bitflags`]**: The [`bitflags`] crate provides a declarative macro for
generating a structured type representing a set of bitflags.
The critical difference between [`bitflags`]' [`bitflags!`][bitflags-macro]
macro and `mycelium-bitfield`'s [`bitfield!`] macro is that the [`bitflags`]
crate only implements bit*flags*, not bit*fields*. 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`][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][mbf-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.
## usage
This crate's API consists of three primary components, the [packing spec
types](#packing-spec-types), the [`bitfield! macro`](#bitfield-macro), and the
[`FromBits` trait](#frombits-trait).
#### packing spec types
The [`pack` module][pack] 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`][pack] module are `const fn`s, allowing
the use of packing specs in const contexts.
See the [module-level docs for `pack`][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 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::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);
```
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.
For example:
```rust
use mycelium_bitfield::{bitfield, 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
}
}
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;
}
}
// Unpacking a typed value with `get` will return that value, or panic if
// the bit pattern is invalid:
let my_bitfield = TypedBitfield::from_bits(0b0011_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);
// 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());
```
See the [`FromBits` trait documentation][`FromBits`] for details on
implementing [`FromBits`] for user-defined types.
[Mycelium]: https://mycelium.elizas.website
[pack]:
https://docs.rs/mycelium-bitfield/latest/mycelium_bitfield/pack/index.html
[`bitfield!`]:
https://docs.rs/mycelium-bitfield/latest/mycelium_bitfield/macro.bitfield.html
[`modular-bitfield`]: https://crates.io/crates/modular-bitfield
[`bitflags`]: https://crates.io/crates/bitflags
[bitflags-macro]: https://docs.rs/bitflags/latest/bitflags/macro.bitflags.html
[`FromBits`]:
https://docs.rs/mycelium-bitfield/latest/mycelium_bitfield/trait.FromBits.html
[mbf-validation]:
https://docs.rs/modular-bitfield/latest/modular_bitfield/#example-extra-safety-guard
[`Pack64`]:
https://docs.rs/mycelium-bitfield/latest/mycelium_bitfield/pack/struct.Pack64.html