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 aU
-typed value, and aU
-typed value, returns a new instance ofSelf
with the bit representation ofvalue
packed into the range represented bypacker
.fn set<U>(&mut self, packer: Self::Packer<U>, value: U) -> &mut Self
: Similar towith
, exceptself
is mutated in place, rather than returning a new nstance ofSelf
fn get<U>(&self, packer: Self::Packer<U>) -> U
: Given one of this type’s generated packing specs for aU
-typed value, unpacks the bit range represented by that value as aU
and returns it. This method panics if the requested bit range does not contain a valid bit pattern for aU
-typed value, as determined byU
’s implementation of theFromBits
trait.fn try_get<U>(&self, packer: Self::Packer<U>) -> Result<U, <U as FromBits>::Error>
: Likeget
, but returns aResult
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 callsassert_valid
can be added to check the bitfield type’s validity.fn display_ascii(&self) -> impl core::fmt::Display
: Returns afmt::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 afmt::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
: TheDebug
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’sfmt::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
andfmt::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 theCopy
implementation for the underlying integer type.Clone
: Behaves identically as theClone
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’sfrom_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’sbits
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.