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 129

The 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 field

This 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 record

The 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 48

The 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 address of 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 run

Swap 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.