A status register, a protocol header, a command byte: systems code is full
of words whose bits mean things. C gives you bit fields with
implementation-defined layout — unusable for a wire format — or shifting
and masking by hand, which is what everyone actually does. Mica’s
bitrecord[N] makes the word a typed value: fields are ordinary names,
their types decide their widths, and the shifting arithmetic exists only in
the generated code.
The example is
BitRecords;
it assumes records and bounds and borrows a page from
Sets, whose bitset[N] is a bit record’s natural field.
The type decides the width
type
Prio = 0 .. 7;
Chan = 0 .. 255;
Gear = (Park, Reverse, Neutral, Drive);
Status = bitrecord[16]
ready : bool;
error : bool;
priority : Prio at 2;
channel : Chan at 5;
rest : Prio at 13;
end;A bool is one bit, the 0 .. 7 subrange takes three, 0 .. 255 takes
eight, an enumeration its ordinal width. Sixteen bits, fully accounted for
— and reading or writing a field is ordinary member access, with the
field’s own type doing its ordinary work:
status.priority := 5; { a Prio: only 0..7 can ever land here }Fields over one 16-bit word
ready 1, priority 5, channel 129The at pin proves, it does not place
The field order derives the layout. An at pin states the position the
datasheet requires — and the compiler proves the two agree:
analyzer error 5272: field 'b' derives at bit offset 3, but its 'at' pin
claims 2: the pin verifies the derived layout, it never places a fieldThis is the declaration-versus-drift story in miniature: the pin is not a placement tool you can contradict yourself with, it is a checked assertion that your record and the hardware’s register still describe the same sixteen bits. Reorder a field and every stale pin refuses.
The whole register stays one value — it assigns, passes and compares as a unit, which is exactly what the register file or the wire wants:
The word travels whole
remote priority 7, local still 5
gear ordinal 3 in a 4-bit recordThe masked case
For the words that arrive raw, case dispatches on binary patterns:
? positions are wildcards, digit separators group as in any binary
literal, and exact values stand beside patterns in the same statement:
case command of
0b00??: kind := 1; { the four low configuration commands }
0b01?0: kind := 2; { even transport commands: 4 and 6 }
5: kind := 3; { an exact value beside the patterns }
0b1???: kind := 4
else
kind := 5
end;A masked case over a command byte
sixteen commands sorted, weighted total 48The example sweeps all sixteen low commands through and checks the weighted total — every path pinned at once. Against the if-and-mask ladder this replaces, the win is not brevity but reviewability: each arm reads directly against the datasheet’s command table, wildcard for wildcard.
What this does not do
- No implementation-defined anything. The layout is the field order, bit zero up; the pins prove it; both backends agree. What C’s standard leaves to the compiler is exactly what a wire format cannot leave to anyone.
- A field is not addressable. You cannot take
addressof three bits; the record travels whole and the fields are access paths, not storage. - Patterns belong to unsigned words and bitsets. The masked arms dispatch on raw bit material — a signed integer’s sign bit has meaning a wildcard would trample, so the selector stays unsigned.
Try it
git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/BitRecords runSwap ready and error in the declaration and watch every at pin below
refuse with 5272 — the datasheet check working for you. Then add
0b0110: above the 0b01?0: arm and see which command changes lanes.
Next
Initializers and leave — the two small features that carry everyday control flow: declarations that start alive, and the one word for leaving early.