Skip to main content

Crate chapa

Crate chapa 

Source
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: i8i128 field 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 readonly or a leading _ prefix
  • Default values: Set a field’s initial value with default = ...
  • Aliases: Expose extra accessor names with alias = "name" or alias = ["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_, and overflowing_ variants of add/sub on 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 reflection feature 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

OptionRequiredDescription
u8 / u16 / u32 / u64 / u128YesBacking storage type
order = msb0 / order = lsb0YesBit numbering convention
width = NNoEffective logical width, must be <= storage width

§#[bits(...)] options

OptionDescription
NSingle bit at index N
N..=MInclusive range from bit N to bit M
N..MHalf-open range (equivalent to N..=(M-1))
readonlySuppress 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 (u8u128), a signed integer (i8i128, 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:

ItemSignature
Constantpub const FOO_SHIFT: u32
Constantpub const FOO_MASK: StorageType
Getterpub const fn foo(&self) -> u8
Setterpub const fn set_foo(&mut self, val: u8)
Builderpub const fn with_foo(self, val: u8) -> Self

Every struct also provides these methods (N is the storage size in bytes):

ItemSignature
Zeroedpub const fn zeroed() -> Self
Raw accesspub const fn from_raw(val: StorageType) -> Self
Raw accesspub const fn raw(&self) -> StorageType
Bytespub const fn to_le_bytes(self) -> [u8; N]
Bytespub const fn to_be_bytes(self) -> [u8; N]
Bytespub const fn to_ne_bytes(self) -> [u8; N]
Bytespub const fn from_le_bytes(bytes: [u8; N]) -> Self
Bytespub const fn from_be_bytes(bytes: [u8; N]) -> Self
Bytespub const fn from_ne_bytes(bytes: [u8; N]) -> Self
Arithmeticpub const fn wrapping_add(self, rhs: StorageType) -> Self (same shape for wrapping_sub, saturating_add, saturating_sub)
Arithmeticpub const fn checked_add(self, rhs: StorageType) -> Option<Self> (same shape for checked_sub)
Arithmeticpub 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:

TraitSignature
BitAndfn bitand<Rhs>(self, rhs: Rhs) -> Self
BitOrfn bitor<Rhs>(self, rhs: Rhs) -> Self
BitXorfn bitxor<Rhs>(self, rhs: Rhs) -> Self
Notfn not(self) -> Self
BitAndAssignfn bitand_assign<Rhs>(&mut self, rhs: Rhs)
BitOrAssignfn bitor_assign<Rhs>(&mut self, rhs: Rhs)
BitXorAssignfn 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 the reflection feature 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§

InvalidBitPattern
Error returned by BitField::try_from_raw and the TryFrom impl generated for #[bitenum] enums when a raw storage value matches no variant.

Traits§

BitField
Trait implemented by every struct produced by the bitfield macro and every enum annotated with #[bitenum].
BitStorage
Trait for types that can be used as the backing storage of a bitfield.

Attribute Macros§

bitenum
The #[bitenum] attribute macro.
bitfield
The #[bitfield] attribute macro.