Crate tartan_bitfield
source ·Expand description
This crate can be used to define structures with accessors for particular bits or bit ranges. Useful for dealing with registers and protocols.
Features
- Performance: Generated code is nearly identical to hand-rolled bit twiddling.
- Safety: Absolutely no unsafe code in the implementation or usage.
- Portability:
#![no_std]
-compatible out of the box.- Unlike bit fields in C, the layout is predictable. See the section about endianness below.
- Unlike Rust’s
#[repr(packed, C)]
, each field has explicit bit ranges, rather than relying on the ordering and size of other fields within the struct. While specifying bit numbers may seem tedious, it can eliminate surprises, and it usually corresponds directly to the way registers and protocols are defined in datasheets.
- Convenience:
- Single-bit flags and multi-bit fields can be defined in the same structure.
- Bit ranges can be accessed as non-primitive, non-integer types (including other
bitfield structs) using appropriate
Into
andFrom
implementations. - The structs implement all the traits you would expect. See the documentation
for
bitfield
. Abitfield_without_debug
macro is also available if you want to provide your own debugging output. - Accessors can be defined in a trait, which is useful for registers where where
some fields are common, but others are only defined in certain states. See
bitfield_accessors
.
Example
bitfield! {
// The structure will be a wrapper for a u32 value.
pub struct Example(u32) {
// Accessors for field `a` will refer to the first four least significant
// bits of the wrapped value, bits 0, 1, 2, and 3.
//
// Note that like normal Rust ranges:
// * `[0..4]` does not include bit 4
// * `[0..=4]` includes bit 4
//
// The accessors will be public, and will take/return the four bits as a `u8`.
[0..4] pub a: u8,
// No accessors cover bits `4..6`. This is legal and can be used for reserved
// bits. However, these bits will still affect equality for the struct as a
// whole.
// Accessors for field `b` will refer to the twelve bits starting at bit 6,
// but they will not be public. They will take/return the 12 bits as a `u16`.
[6..=17] b: u16,
// Note that this bit range overlaps with `b`. This is allowed.
[16..20] pub c: u8,
// Accessors for field `d` will take/return a boolean and refer to a single
// bit. Note that the `bool` is implied and not specified after the name.
[25] pub d,
// This will cover the 6 most significant bits of the wrapped value, but
// the getters will take/return a `SubFields` struct instead of `u8`. This is
// useful for nested bitfields, but the `A as B` syntax works for any `B`
// which implements `Into<A>` and `From<A>`.
[26..32] pub e: u8 as SubFields,
}
}
bitfield! {
// All accessors on this field use booleans and refer to single bits
pub struct SubFields(u8) {
[0] pub zero,
[1] pub one,
[2] pub two,
[3] pub three,
[4] pub four,
[5] pub five,
}
}
// The struct can be initialized with a u32 value
let x = Example(0xfa84_9e1b);
assert_eq!(x.a(), 0xb_u8);
assert_eq!(x.b(), 0x278_u16); // Private, but still can be used within the module
assert_eq!(x.c(), 0x4_u8);
assert_eq!(x.d(), true);
assert_eq!(x.e(), SubFields(0x3e_u8));
assert_eq!(x.e().zero(), false);
assert_eq!(x.e().five(), true);
// It can also be converted Into and From its underlying representation
let n: u32 = x.into();
let y: Example = n.into();
assert_eq!(n, 0xfa84_9e1b);
assert_eq!(x, y);
// Setters are all prefixed with `set_`. They have the same visibility as the getters.
let mut z = Example::default();
z.set_a(0xb);
z.set_b(0x278);
z.set_c(0x4);
z.set_d(true);
z.set_e(SubFields(0x3e));
assert_eq!(z, Example(0xfa04_9e0b));
// Reserved ranges influence equality, and they are all zero on `z`.
assert_ne!(z, x);
// Alternatively, you can use the `with_` methods, which return a new value instead
// of mutating in place.
let mut w = x
.with_a(0x6)
.with_b(0x9f3)
.with_c(0xd)
.with_d(false)
.with_e(SubFields(0x2b));
assert_eq!(w, Example(0xac8d_7cd6));
assert_eq!(x, Example(0xfa84_9e1b));
For lots more examples, see the Tartan OS project that this crate was spun off from.
Endiannness and Bit Numbering
Each bitfield wraps an underlying integer type. In the example above, Example(u32)
wraps a u32
. Bit numbers within the macro refer to the bits of the logical value,
starting from the least significant bit = 0. They are not dependent on the order of
the bytes of the u32
representation in memory, a.k.a. endianness.
The endianness of the underlying value is platform dependent. This is no different than any other integer value, and the context determines whether you need to worry about it.
- If the underlying representation is a
u8
, then byte order is irrelevant. - If you are reading from a register, it’s likely you want native byte order and don’t need to do anything special.
- If you are working with a network or bus protocol, it’s likely you are serializing
or deserializing from a byte array. To convert using a specific endianness
regardless of platform, use the normal methods: for example, the builtins
u32::from_be_bytes
andu64::to_le_bytes
, or a crate like byteorder.
Alternatives
I have been using this in my personal OS project for a while, and it meets my needs better than other options. But you may be interested in a few other crates:
- bitfield: Similar approach for accessors and bit ranges, but a less obvious syntax.
- bitflags: Works well for single-bit flags that can be viewed as a collection.
- bitvec: Another collection type to view memory as a sequence of bits. Less focused on defining domain-specific structs.
- modular-bitfield: Field ordering and widths determine bit ranges. Structs can only be converted to byte arrays, and only in little-endian order, regardless of platform endianness. This can be undesirable when working with registers.
- packed_struct: Lots of
options, including bit numbering and endianness conversions. Structs are held
in unpacked form in memory, and only converted to packed form for serialization.
Depending on your access patterns, this may be better or worse (or it may not
matter at all).
- For an analogue to
pack_struct
’sPrimitiveEnum
, see thetartan-c-enum
crate.
- For an analogue to
Macros
Bitfield
trait.Traits
x as T
.