chapa
Bitfield structs, batteries included!
chapa exposes a single attribute macro, #[bitfield], that turns an ordinary
struct into a newtype backed by a single primitive. Every field maps to an exact
range of bits and gets a generated getter, setter, and with_* builder.
Features
- MSB0 and LSB0 support: Naturally write bit orders as per datasheet
- Signed fields:
i8...i128field types with automatic sign extension - Enum fields: Use enums as bitfield fields with
#[derive(BitEnum)] - Nested bitfields: Embed one bitfield struct inside another
- Readonly fields: Suppress setter generation with
readonlyor a leading_prefix - Default values: Give fields a
default = ...and#[derive(Default)]to bake them in - Aliases: Expose extra accessor names with
alias = "name"oralias = ["a", "b"] - Overlays: Allow multiple logically distinct field groups to share the same bit range
- Bitwise operators:
&,|,^,!,&=,|=,^=with the backing storage type work directly on the struct - Bit extraction:
extract_bits!masks a value to keep only the specified bit ranges - Reflection: Opt into the
reflectionfeature for compile-time field metadata (FIELDS, bit positions, enum variants)
MSRV
Requires Rust 1.83 or newer (the generated getters, setters, and with_*
builders are const fn).
Quick start
use bitfield;
// An 8-bit status register, bit 0 is the LSB
let r = zeroed
.with_enabled
.with_mode;
assert!;
assert_eq!;
assert_eq!; // accessible as `reserved`, not `_reserved`
#[bitfield(...)] options
| Option | Required | Description |
|---|---|---|
u8 / u16 / u32 / u64 / u128 |
Yes | Backing storage type |
order = msb0 / order = lsb0 |
Yes | Bit numbering convention |
width = N |
No | Effective logical width, must be <= storage width |
#[bits(...)] options
| Option | Description |
|---|---|
N |
Single bit at index N |
N..=M |
Inclusive range from bit N to bit M |
N..M |
Half-open range (equivalent to N..=(M-1)) |
readonly |
Suppress set_* and with_* generation |
default = <expr> |
Starting value applied by default() |
alias = "name" |
Generate additional accessor under name |
alias = ["a","b"] |
Multiple aliases |
overlay = "group" |
Allow overlap with fields in other overlay groups |
A field's type may be bool (single bit), an unsigned integer (u8...u128),
a signed integer (i8...i128, two's-complement: sign-extended on read,
truncated to the field width on write), a #[derive(BitEnum)] enum, or another
bitfield struct.
MSB-0 example
use bitfield;
// A 32-bit value where bit 0 is the most-significant bit
let cw = zeroed
.with_opcode
.with_dst;
assert_eq!;
Enum fields
Use #[derive(BitEnum)] on an enum to implement BitField, allowing it to be
used as a bitfield field type. The enum must also derive Copy + Clone
itself and mark exactly one variant #[fallback].
use ;
let dc = zeroed
.with_enable
.with_fmt;
assert_eq!;
// Unrecognized raw values are handled two ways:
// - from_raw (and the getter dc.fmt()) coerce them to the #[fallback] variant
// - try_from_raw / TryFrom reject them, so corrupt input can be detected
assert_eq!; // coerced to #[fallback]
assert!; // detected
assert_eq!; // TryFrom<u8>
Nested bitfields
A field whose type implements chapa::BitField (i.e. any type annotated with
#[bitfield]) can be used as a nested field.
use bitfield;
let nibble = zeroed.with_high.with_low;
let word = zeroed.with_top.with_bottom;
assert_eq!;
assert_eq!;
assert_eq!;
Overlay groups
Fields in different overlay groups may share bit ranges. This is useful for instruction formats where the same bits are interpreted differently depending on other bits. This is useful for instruction decoding, but also to handle specific MMIO registers that change their meaning depending on certain encoded bits.
use bitfield;
let r_form = zeroed.with_opcode.with_rs.with_ra.with_rb;
assert_eq!;
let i_form = zeroed.with_opcode.with_dst.with_imm;
assert_eq!;
assert_eq!; // Both names cover bits 6..=10
Constructors and default values
Every struct gets a const fn zeroed() that returns an all-zero instance. There
is no new(). To give a field its own starting value, add default = <expr> and
#[derive(Default)]; the generated default() applies those values, while
zeroed() and from_raw always ignore them.
default works on any field type (bool, integer, #[derive(BitEnum)] enum,
or nested bitfield, e.g. default = Mode::On), including readonly ones.
Values wider than the field truncate to its width, exactly like a setter.
Declaring a default without #[derive(Default)] is a compile error, since the
value would otherwise never be applied.
use bitfield;
let c = default;
assert_eq!;
assert_eq!;
assert_eq!; // no default -> zero
// zeroed() and from_raw never inject defaults
assert_eq!;
assert_eq!;
Bitwise operations
Every bitfield struct implements BitAnd, BitOr, BitXor, Not,
BitAndAssign, BitOrAssign, and BitXorAssign against its backing storage type.
use bitfield;
const HIGH_BYTE: u32 = 0xFF00_0000;
let current = from_raw;
let incoming: u32 = 0xABCD_EF01;
// Keep the low 24 bits of `current`, replacing only its high byte.
let updated = | ;
assert_eq!;
assert!;
assert_eq!;
Bit extraction
extract_bits! keeps only the specified bit positions from a value, zeroing all others.
Bits can be single indices or inclusive start..=end ranges.
For raw integers, specify the ordering and type explicitly:
use extract_bits;
let val: u32 = 0xFFFF_FFFF;
// MSB0: keep bits 0, 5–9, 16–31
let masked = extract_bits!;
assert_eq!;
// LSB0: keep bits 0–3 and 12–15
let masked = extract_bits!;
assert_eq!;
For chapa bitfield structs, omit the ordering. It is deduced from the struct's
#[bitfield] definition, and the result has the same struct type:
use ;
let packet = from_raw;
let masked: Packet = extract_bits!;
assert_eq!;
The explicit form (msb0 u32) emits const MASK: T = ..., so the mask is guaranteed to be computed at compile time. The struct form calls an #[inline] helper; LLVM should constant-fold the mask in practice, but there is no language-level guarantee.
Reflection
Enable the reflection feature to get compile-time field metadata for every
#[bitfield] struct and #[derive(BitEnum)] enum:
[]
= { = "0.6", = ["reflection"] }
Each bitfield struct gains an inherent FIELDS: &'static [FieldInfo] const
describing its fields: their accessor name, bit position, aliases and how the
raw bits should be interpreted. Offsets and widths are physical (in
storage-value "coordinates"), so a field's value is always
(raw >> offset) & ((1 << width) - 1) regardless of msb0/lsb0 ordering.
Nested enum and struct fields carry their own variant table / fields.
#
#
FieldKind distinguishes Bool, Uint, Sint, Enum(&EnumInfo) and
Struct(&[FieldInfo]). The types (FieldInfo, FieldKind, EnumInfo) and the
Reflect trait are re-exported at the crate root when the feature is on.
Generated API
For a field foo: u8 spanning bits 4..=7 the macro generates:
| Item | Signature |
|---|---|
| Constant | pub const FOO_SHIFT: u32 |
| Constant | pub const FOO_MASK: StorageType |
| Getter | pub const fn foo(&self) -> u8 |
| Setter | pub const fn set_foo(&mut self, val: u8) |
| Builder | pub const fn with_foo(self, val: u8) -> Self |
Additionally, every struct implements the following traits:
| Trait | Signature |
|---|---|
BitAnd |
fn bitand(self, rhs: StorageType) -> Self |
BitOr |
fn bitor(self, rhs: StorageType) -> Self |
BitXor |
fn bitxor(self, rhs: StorageType) -> Self |
Not |
fn not(self) -> Self |
BitAndAssign |
fn bitand_assign(&mut self, rhs: StorageType) |
BitOrAssign |
fn bitor_assign(&mut self, rhs: StorageType) |
BitXorAssign |
fn bitxor_assign(&mut self, rhs: StorageType) |