Expand description
§chapa
Bitfield structs, batteries included!
chapa exposes an 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. A
companion attribute macro, bitenum, makes a C-like enum usable as a field
type.
§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
#[bitenum] - Nested bitfields: Embed one bitfield struct inside another
- Readonly fields: Suppress setter generation with
readonlyor a leading_prefix - Default values: Set a field’s initial value with
default = ... - 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 - Raw arithmetic:
wrapping_,saturating_,checked_, andoverflowing_variants ofadd/subon the raw storage value - Bit extraction:
extract_bits!masks a value to keep only the specified bit ranges - Bit insertion:
place_bits!shifts a value into a range,insert_bits!merges already-positioned bits - 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 chapa::bitfield;
// An 8-bit status register, bit 0 is the LSB
#[bitfield(u8, order = lsb0)]
#[derive(Debug, PartialEq)]
pub struct StatusReg {
#[bits(0)] enabled: bool,
#[bits(1..=3)] mode: u8,
#[bits(4..=7)] _reserved: u8, // Leading "_" makes the field readonly
}
let r = StatusReg::zeroed()
.with_enabled(true)
.with_mode(5);
assert!(r.enabled());
assert_eq!(r.mode(), 5);
assert_eq!(r.reserved(), 0); // 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 #[bitenum] enum, or another
bitfield struct.
§MSB-0 example
use chapa::bitfield;
// A 32-bit value where bit 0 is the most-significant bit
#[bitfield(u32, order = msb0)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ControlWord {
#[bits(0..=3)] opcode: u8,
#[bits(4..=7)] dst: u8,
#[bits(8..=31, readonly)] payload: u32,
}
let cw = ControlWord::zeroed()
.with_opcode(0xA)
.with_dst(0x3);
assert_eq!(cw.raw(), 0xA300_0000);§Enum fields
Use #[bitenum] on an enum to automatically implement BitField,
allowing it to be used as a bitfield field type. The enum must mark exactly
one variant #[fallback].
from_raw coerces any unrecognized raw value to the #[fallback] variant;
BitField::try_from_raw (or TryFrom) reports it as
InvalidBitPattern instead.
use chapa::{bitfield, bitenum};
#[bitenum]
#[derive(Debug, PartialEq)]
pub enum VideoFormat {
Ntsc = 0,
Pal = 1,
Mpal = 2,
#[fallback]
Debug = 3,
}
#[bitfield(u16, order = lsb0)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DisplayConfig {
#[bits(0)] enable: bool,
#[bits(1..=2)] fmt: VideoFormat,
}
let dc = DisplayConfig::zeroed()
.with_enable(true)
.with_fmt(VideoFormat::Pal);
assert_eq!(dc.fmt(), VideoFormat::Pal);§Nested bitfields
A field whose type implements BitField (i.e. any type annotated with
#[bitfield]) can be used as a nested field.
use chapa::bitfield;
#[bitfield(u8, order = msb0, width = 4)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Nibble {
#[bits(0..=1)] high: u8,
#[bits(2..=3)] low: u8,
}
#[bitfield(u32, order = msb0)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Word {
#[bits(0..=3)] top: Nibble,
#[bits(28..=31)] bottom: u8,
}
let nibble = Nibble::zeroed().with_high(2).with_low(1);
let word = Word::zeroed().with_top(nibble).with_bottom(0xA);
assert_eq!(nibble.raw(), 0b1001);
assert_eq!(word.raw(), 0x9000_000A);
assert_eq!(word.top(), nibble);§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, such as instruction decoding or MMIO registers that change meaning based on encoded bits.
use chapa::bitfield;
#[bitfield(u32, order = msb0)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Instr {
#[bits(0..=5)] opcode: u8,
#[bits(6..=10, overlay = "r_form")] rs: u8,
#[bits(11..=15, overlay = "r_form")] ra: u8,
#[bits(16..=20, overlay = "r_form")] rb: u8,
#[bits(6..=10, overlay = "i_form")] dst: u8,
#[bits(11..=31, overlay = "i_form")] imm: u32,
}
let r_form = Instr::zeroed().with_opcode(0x20).with_rs(3).with_ra(4).with_rb(5);
assert_eq!((r_form.rs(), r_form.ra(), r_form.rb()), (3, 4, 5));
let i_form = Instr::zeroed().with_opcode(0x08).with_dst(7).with_imm(0x1_2345);
assert_eq!((i_form.dst(), i_form.imm()), (7, 0x1_2345));
assert_eq!(i_form.rs(), i_form.dst()); // Both names cover bits 6..=10§Constructors and default values
Every struct has a const fn zeroed() that returns an all-zero value. There
is no new(). Add default = <expr> to give a field a different initial
value. This automatically implements Default. The zeroed() and
from_raw() methods do not apply field defaults. If no fields have defaults,
you can still use #[derive(Default)] to make default() return zeroed().
Works on any field type (bool, integer, #[bitenum] enum, or
nested bitfield, e.g. default = Mode::On), including readonly ones;
values wider than the field truncate to its width, like a setter.
use chapa::bitfield;
#[bitfield(u16, order = lsb0)]
#[derive(Debug, PartialEq)]
pub struct Config {
#[bits(0)] enabled: bool,
#[bits(1..=3, default = 5)] mode: u8,
#[bits(8, default = true)] ready: bool,
}
let c = Config::default();
assert_eq!(c.mode(), 5);
assert_eq!(c.ready(), true);
assert_eq!(c.enabled(), false); // no default -> zero
// zeroed() and from_raw never inject defaults
assert_eq!(Config::zeroed().mode(), 0);
assert_eq!(Config::from_raw(0).mode(), 0);§Bitwise operations
Every bitfield struct implements BitAnd,
BitOr, BitXor,
Not, BitAndAssign,
BitOrAssign, and
BitXorAssign. The right-hand operand may be the
raw storage type or any bitfield backed by the same storage type; the result
keeps the left-hand bitfield type.
use chapa::bitfield;
#[bitfield(u32, order = msb0)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct StatusReg {
#[bits(0)] enabled: bool,
#[bits(1..=7)] flags: u8,
}
const HIGH_BYTE: u32 = 0xFF00_0000;
let current = StatusReg::from_raw(0x1234_5678);
let incoming: u32 = 0xABCD_EF01;
// Keep the low 24 bits of `current`, replacing only its high byte.
let updated = (current & !HIGH_BYTE) | (incoming & HIGH_BYTE);
assert_eq!(updated.raw(), 0xAB34_5678);
assert!(updated.enabled());
assert_eq!(updated.flags(), 0x2B);§Raw arithmetic
Every bitfield struct provides wrapping_add, wrapping_sub,
saturating_add, saturating_sub, checked_add, checked_sub,
overflowing_add, and overflowing_sub, mirroring the methods on the
backing storage type. They operate on the full raw storage value, exactly
like raw(): carries and borrows propagate across field boundaries, and
bit ordering plays no role.
use chapa::bitfield;
#[bitfield(u16, order = lsb0)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Counter {
#[bits(0..=7)] low: u8,
#[bits(8..=15)] high: u8,
}
let c = Counter::from_raw(0xFFFF).wrapping_add(1);
assert_eq!(c.raw(), 0x0000);
// Carries cross field boundaries, just like on the raw integer.
let c = Counter::from_raw(0x00FF).wrapping_add(1);
assert_eq!((c.low(), c.high()), (0x00, 0x01));
assert_eq!(Counter::from_raw(0xFFFF).checked_add(1), None);
assert_eq!(Counter::from_raw(0x0005).saturating_sub(0x10).raw(), 0x0000);
let (c, borrowed) = Counter::from_raw(0x0000).overflowing_sub(1);
assert_eq!(c.raw(), 0xFFFF);
assert!(borrowed);If you need wrap-around at a single field’s width instead, the setters
already truncate to the field width, so
c.set_low(c.low().wrapping_add(1)) wraps correctly within low alone.
§Bit extraction with extract_bits!
extract_bits! keeps only the specified bit positions from a value, zeroing all others.
Bits can be single indices or inclusive ranges; the ordering and storage type are either
supplied explicitly (for raw integers) or deduced from the struct’s BitField impl.
use chapa::{bitfield, extract_bits};
#[bitfield(u32, order = msb0)]
#[derive(Copy, Clone)]
pub struct Packet {
#[bits(0)] priority: bool,
#[bits(5..=9)] kind: u8,
#[bits(16..=31)] payload: u16,
}
let packet = Packet::from_raw(0xFFFF_FFFF);
// Struct form: ordering deduced; returns Packet with non-selected bits zeroed
let masked: Packet = extract_bits!(packet; 0, 5..=9, 16..=31);
// Explicit form for raw integers: const-evaluated mask
let raw: u32 = extract_bits!(msb0 u32; 0xFFFF_FFFFu32; 0, 5..=9, 16..=31);
assert_eq!(raw, 0x87C0_FFFF);
assert_eq!(raw, masked.raw());See the extract_bits! documentation for full syntax details.
§Writing bit ranges with place_bits! and insert_bits!
These macros update bit ranges without using field setters:
place_bits!shifts a right-aligned value into one bit or range.insert_bits!copies already-positioned bits into one or more ranges.
Both macros support explicit msb0 and lsb0 forms. With a bitfield value,
the ordering is inferred. They return the updated value instead of changing
it in place.
use chapa::{bitfield, place_bits, insert_bits};
#[bitfield(u32, order = lsb0)]
pub struct Reg {
#[bits(0..=7)] b0: u8,
#[bits(8..=15)] b1: u8,
#[bits(16..=31)] hi: u16,
}
// Write 0xAB to bits 8..=15.
let mut reg = Reg::from_raw(0xDEAD_0000);
reg = place_bits!(reg; 8..=15; 0xABu8);
assert_eq!(reg.b1(), 0xAB);
assert_eq!(reg.hi(), 0xDEAD);
// Replace the low two bytes with already-positioned bits.
reg = insert_bits!(reg; 0..=15; 0x0000_1234u32);
assert_eq!(reg.raw(), 0xDEAD_1234);§Reflection
Enable the reflection feature to get compile-time field metadata for every
#[bitfield] struct and #[bitenum] enum. Each struct gains an
inherent FIELDS: &'static [FieldInfo] const; the types (FieldInfo,
FieldKind, EnumInfo) and the Reflect trait are re-exported at the
crate root when the feature is on. 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.
use chapa::{bitfield, bitenum, FieldKind};
#[bitenum]
pub enum Mode { Off = 0, On = 1, #[fallback] Reserved = 3 }
#[bitfield(u16, order = lsb0)]
#[derive(Copy, Clone)]
pub struct Reg {
#[bits(0)] enabled: bool,
#[bits(1..=2)] mode: Mode,
#[bits(4..=7)] count: u8,
}
let mode = Reg::FIELDS.iter().find(|f| f.name == "mode").unwrap();
assert_eq!((mode.offset, mode.width), (1, 2));
if let FieldKind::Enum(info) = mode.kind {
assert_eq!(info.variants, &[(0, "Off"), (1, "On"), (3, "Reserved")]);
} else {
panic!("mode should be reflected as an enum");
}§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 |
Every struct also provides these methods (N is the storage size in bytes):
| Item | Signature |
|---|---|
| Zeroed | pub const fn zeroed() -> Self |
| Raw access | pub const fn from_raw(val: StorageType) -> Self |
| Raw access | pub const fn raw(&self) -> StorageType |
| Bytes | pub const fn to_le_bytes(self) -> [u8; N] |
| Bytes | pub const fn to_be_bytes(self) -> [u8; N] |
| Bytes | pub const fn to_ne_bytes(self) -> [u8; N] |
| Bytes | pub const fn from_le_bytes(bytes: [u8; N]) -> Self |
| Bytes | pub const fn from_be_bytes(bytes: [u8; N]) -> Self |
| Bytes | pub const fn from_ne_bytes(bytes: [u8; N]) -> Self |
| Arithmetic | pub const fn wrapping_add(self, rhs: StorageType) -> Self (same shape for wrapping_sub, saturating_add, saturating_sub) |
| Arithmetic | pub const fn checked_add(self, rhs: StorageType) -> Option<Self> (same shape for checked_sub) |
| Arithmetic | pub const fn overflowing_add(self, rhs: StorageType) -> (Self, bool) (same shape for overflowing_sub) |
The byte conversions and arithmetic methods operate on the full storage
value, matching raw() and from_raw().
Additionally, every struct implements the following traits:
| Trait | Signature |
|---|---|
BitAnd | fn bitand<Rhs>(self, rhs: Rhs) -> Self |
BitOr | fn bitor<Rhs>(self, rhs: Rhs) -> Self |
BitXor | fn bitxor<Rhs>(self, rhs: Rhs) -> Self |
Not | fn not(self) -> Self |
BitAndAssign | fn bitand_assign<Rhs>(&mut self, rhs: Rhs) |
BitOrAssign | fn bitor_assign<Rhs>(&mut self, rhs: Rhs) |
BitXorAssign | fn bitxor_assign<Rhs>(&mut self, rhs: Rhs) |
Rhs may be the backing storage type or a bitfield with the same backing
storage type.
Re-exports§
pub use mask::lsb0_mask;pub use mask::msb0_mask;pub use reflection::EnumInfo;pub use reflection::FieldInfo;pub use reflection::FieldKind;pub use reflection::Reflect;
Modules§
- mask
- Utilities for masking raw integers by bit ranges.
- reflection
- Compile-time field metadata emitted by the
#[bitfield]and#[bitenum]macros when thereflectionfeature is enabled.
Macros§
- extract_
bits - Keep only the specified bits from a value.
- insert_
bits - Copies already-positioned bits into selected ranges of a value.
- place_
bits - Shift a right-aligned value into a single bit range of a value.
Structs§
- Invalid
BitPattern - Error returned by
BitField::try_from_rawand theTryFromimpl generated for#[bitenum]enums when a raw storage value matches no variant.
Traits§
- BitField
- Trait implemented by every struct produced by the
bitfieldmacro and every enum annotated with#[bitenum]. - BitStorage
- Trait for types that can be used as the backing storage of a bitfield.