Skip to main content

bnb/guide/
bitfields.rs

1//! `#[bitfield]` — pack typed fields into one backing integer.
2//!
3//! A `#[bitfield]` collapses a struct of `Bits`-typed fields into a single unsigned
4//! integer, generating accessors that shift and mask. It is the tool for a run of
5//! sub-byte fields that together fill one word (a flags/opcode byte, a VLAN tag, an
6//! IPv4 first byte).
7//!
8//! ```
9//! use bnb::{bitfield, u4};
10//!
11//! #[bitfield(u8, bits = msb)]
12//! #[derive(Clone, Copy)]
13//! struct VersionIhl {
14//!     version: u4,   // high nibble
15//!     ihl: u4,       // low nibble
16//! }
17//!
18//! let b = VersionIhl::new().with_version(u4::new(4)).with_ihl(u4::new(5));
19//! assert_eq!(b.to_raw(), 0x45);          // the classic IPv4 first byte
20//! assert_eq!(b.version().value(), 4);
21//! ```
22//!
23//! # Generated API
24//!
25//! For a field `f: T`, you get `f() -> T`, `with_f(T) -> Self` (consuming, chainable),
26//! and `set_f(&mut self, T)`. Plus `new()` (all-zero), `to_raw()`/`from_raw()`, and
27//! allocation-free byte conversions: `to_bytes`/`from_bytes` serialize in the **declared**
28//! byte order (`bytes = big|little`), while `to_be_bytes`/`to_le_bytes`/`from_be_bytes`/`from_le_bytes`
29//! force a specific endianness (the override). The type also implements [`Bits`](crate::Bits)
30//! and [`Bitfield`](crate::Bitfield), so it nests in another bitfield or a `#[bin]` message.
31//!
32//! ```
33//! use bnb::{bitfield, u4};
34//! # #[bitfield(u8, bits = msb)] #[derive(Clone, Copy)] struct VersionIhl { version: u4, ihl: u4 }
35//! let mut b = VersionIhl::new().with_version(u4::new(4)).with_ihl(u4::new(5));
36//! b.set_ihl(u4::new(6));
37//! assert_eq!(b.ihl().value(), 6);
38//! assert_eq!(VersionIhl::from_be_bytes([0x45]).version().value(), 4);
39//! ```
40//!
41//! # Bit order vs. byte order — two independent knobs
42//!
43//! - `bits = msb | lsb` (default `msb`): does the **first** declared field land in the
44//!   high or low bits of the backing integer. `msb` matches the ASCII-art layouts in
45//!   RFCs (first field drawn leftmost = most significant).
46//! - `bytes = big | little` (default `big`): the byte order `to_bytes`/`from_bytes` use
47//!   when serializing the backing integer.
48//!
49//! They are orthogonal *here*: a bitfield packs its fields into the backing integer (bit
50//! order), then serializes that whole integer with the declared byte order — two genuinely
51//! independent steps. (At the `#[bin]` *message* layer the two instead compose by the
52//! natural-layout rule — a byte-multiple value is byte-swapped only when the declared byte
53//! order differs from the bit order's natural layout; see the
54//! [`bin_codec`](super::bin_codec) guide.) The same fields, packed `msb`, declared with two
55//! different byte orders — `to_bytes` honors each declaration:
56//!
57//! ```
58//! use bnb::{bitfield, u4};
59//!
60//! #[bitfield(u16, bits = msb, bytes = big)]
61//! #[derive(Clone, Copy)]
62//! struct Be { hi: u4, mid: u8, lo: u4 }
63//!
64//! #[bitfield(u16, bits = msb, bytes = little)]
65//! #[derive(Clone, Copy)]
66//! struct Le { hi: u4, mid: u8, lo: u4 }
67//!
68//! let be = Be::new().with_hi(u4::new(0xA)).with_mid(0xBC).with_lo(u4::new(0xD));
69//! let le = Le::new().with_hi(u4::new(0xA)).with_mid(0xBC).with_lo(u4::new(0xD));
70//! assert_eq!(be.to_bytes(), [0xAB, 0xCD]); // declared `big` -> big-endian bytes
71//! assert_eq!(le.to_bytes(), [0xCD, 0xAB]); // same logical value, declared `little`
72//!
73//! // `to_be_bytes`/`to_le_bytes` ignore the declaration — use them only to override it.
74//! assert_eq!(le.to_be_bytes(), [0xAB, 0xCD]);
75//! ```
76//!
77//! # Field widths: inferred, explicit, or ranged
78//!
79//! In order of precedence:
80//!
81//! 1. **Inferred** (no attribute): the field's width is `<T as Bits>::BITS`. Fields
82//!    pack adjacently in declaration order. This is the common case.
83//! 2. **`#[bits(N)]`**: an explicit width, still auto-placed. Useful when a field's
84//!    type is wider than the bits it should occupy.
85//! 3. **`#[bits(A..=B)]`**: an absolute, inclusive bit range — fully manual layout,
86//!    the equivalent of `bitbybit`'s `bits = A..=B`. Use ranges on **every** field, or
87//!    on none; the two styles can't be mixed in one struct.
88//!
89//! With inferred widths the declared total is the sum of the fields (gaps are not
90//! transmitted); with ranges the width is the whole backing integer (gaps are real
91//! reserved bits on the wire).
92//!
93//! ```
94//! use bnb::bitfield;
95//!
96//! // Manual layout: two fields with a deliberate 3-bit gap inside a u32.
97//! #[bitfield(u32, bits = msb)]
98//! #[derive(Clone, Copy)]
99//! struct Manual {
100//!     #[bits(20..=31)] tag: bnb::u12,   // top 12 bits
101//!     #[bits(0..=16)]  body: bnb::u17,  // low 17 bits; bits 17..=19 are reserved
102//! }
103//!
104//! let m = Manual::new().with_tag(bnb::u12::new(0xABC)).with_body(bnb::u17::new(1));
105//! assert_eq!(m.tag().value(), 0xABC);
106//! assert_eq!(m.body().value(), 1);
107//! ```
108//!
109//! A reversed range (`#[bits(31..=20)]`) is a clear compile error, not a silent
110//! overflow, and field widths that exceed the backing integer fail a const assert.
111//!
112//! # Nesting
113//!
114//! Because a `#[bitfield]` is itself a `Bits` value, bitfields nest. A 5-bit field of
115//! a nested type contributes exactly 5 bits to its parent:
116//!
117//! ```
118//! use bnb::{bitfield, BitEnum, u3, u5};
119//!
120//! #[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
121//! #[bit_enum(u3)]
122//! enum Op { Read, Write, #[catch_all] Other(u3) }
123//!
124//! #[bitfield(u8, bits = msb)]
125//! #[derive(Clone, Copy)]
126//! struct Cmd { op: Op, addr: u5 }      // 3 + 5 = 8 bits exactly
127//!
128//! let c = Cmd::new().with_op(Op::Write).with_addr(u5::new(0x11));
129//! assert_eq!(c.op(), Op::Write);
130//! assert_eq!(c.addr().value(), 0x11);
131//! ```
132//!
133//! See [`enums`](super::enums) and [`flags`](super::flags) for the field types that
134//! nest here, and [`composition`](super::composition) for the full picture.
135//!
136//! # `#[view]` — a contextual typed view whose meaning depends on a sibling
137//!
138//! Some fields can't be interpreted from their own bits alone — the same bits mean
139//! different things depending on a *sibling* field. (NXDN's LICH: two channel bits
140//! read one way outbound and another inbound, with the direction bit alongside them.)
141//! Because a `#[bitfield]` is random-access, a field's accessor can just read that
142//! sibling — no cursor look-ahead. `#[view(bits = N, read = |raw, s| …, write = |v| …)]`
143//! stores the raw `N` bits and materializes a typed value: `read` receives the raw bits
144//! and `&Self` (call sibling getters for context), and `write` maps the typed value back
145//! to raw bits (context-free). The raw type may be a `uN`, an enum — any
146//! [`Bits`](crate::Bits) type.
147//!
148//! When the raw type is visible to the macro — the `read` closure's first-parameter
149//! annotation (`|raw: u2, s| …`), the `write` closure's return annotation, or an
150//! explicit `raw = <ty>` argument (for `read`/`write` given as fn paths) — the closure
151//! bodies are **inlined** and the accessors are `const fn`, so anything they call must
152//! be `const` too (like `Kind`'s helpers below). For a body that needs non-`const`
153//! operations, add the `dynamic` argument: the closures are then called at runtime,
154//! the accessors are not `const`, and the raw type is inferred. Fully unannotated
155//! closures also keep that runtime form — quietly, since they compiled before the
156//! accessors were `const`. To **assert** const-ness instead, add the `const`
157//! argument: any quiet fallback (invisible raw type, a `write` body that can't be
158//! inlined) becomes a compile error. `const` and `dynamic` are mutually exclusive.
159//!
160//! ```
161//! use bnb::{bitfield, u2, u3};
162//!
163//! #[derive(Debug, PartialEq, Eq, Clone, Copy)]
164//! enum Kind { A, B, Other(u2) }
165//! impl Kind {
166//!     const fn read(bits: u2, outbound: bool) -> Self {
167//!         match (outbound, bits.value()) {
168//!             (true, 0b00) => Kind::A,
169//!             (false, 0b01) => Kind::B,
170//!             _ => Kind::Other(bits),
171//!         }
172//!     }
173//!     const fn bits(self) -> u2 {
174//!         match self { Kind::A => u2::new(0), Kind::B => u2::new(1), Kind::Other(b) => b }
175//!     }
176//! }
177//!
178//! #[bitfield(u8, bits = msb)]
179//! #[derive(Clone, Copy)]
180//! struct Lich {
181//!     header: u3,
182//!     #[view(
183//!         bits = 2,
184//!         read = |raw: u2, s: &Self| Kind::read(raw, s.outbound()),
185//!         write = |v: Kind| v.bits(),
186//!     )]
187//!     kind: Kind,
188//!     outbound: bool,   // the context `kind` reads — a sibling
189//!     trailing: u2,
190//! }
191//!
192//! let outbound = Lich::new().with_kind(Kind::A).with_outbound(true);
193//! assert_eq!(outbound.kind(), Kind::A);                     // outbound && bits 00 → A
194//! // Same stored bits (00), different sibling → different meaning:
195//! let inbound = Lich::new().with_kind(Kind::Other(u2::new(0))).with_outbound(false);
196//! assert_eq!(inbound.kind(), Kind::Other(u2::new(0)));
197//! ```