Expand description
Whole-struct wire mapping — a logical type that serializes via a separate wire type.
directives showed #[br(map)]/#[bw(map)] transforming one field
between its wire value and a friendlier type. The same idea applies to a whole struct: a
logical type whose on-the-wire form is a different wire type. The struct’s own fields are
the logical data — never read or written directly; the wire type owns the bytes.
There are two forms:
- Conversion-trait form —
#[bin(wire = WireType)], driven by standardFrom/TryFromimpls you write. The transitions live in namedimplblocks — a clean home, reusable anywhere in your program — and the wire type may be variable-length. - Closure form —
#[bin(map = …, bw_map = …)], with the transitions inline in the attribute. Quick for a small fixed mapping.
§The conversion-trait form
#[bin(wire = WireType)] needs From<WireType> for Self (decode) and From<&Self> for WireType (encode). Decode reads WireType then Self::from(wire); encode does
WireType::from(&self) then writes.
use bnb::bin;
// The wire form: a normal #[bin] message.
#[bin(big)]
#[derive(Debug, Clone, PartialEq)]
struct WireColor { r: u8, g: u8, b: u8 }
// The logical form: a packed u32, mapped to/from WireColor.
#[bin(wire = WireColor)]
#[derive(Debug, Clone, PartialEq)]
struct Color { rgb: u32 }
impl From<WireColor> for Color {
fn from(w: WireColor) -> Self {
Color { rgb: (w.r as u32) << 16 | (w.g as u32) << 8 | w.b as u32 }
}
}
impl From<&Color> for WireColor {
fn from(c: &Color) -> Self {
WireColor { r: (c.rgb >> 16) as u8, g: (c.rgb >> 8) as u8, b: c.rgb as u8 }
}
}
let c = Color { rgb: 0x11_22_33 };
assert_eq!(c.to_bytes().unwrap(), [0x11, 0x22, 0x33]); // mapped onto the wire
assert_eq!(Color::decode_exact(&[0x11, 0x22, 0x33]).unwrap(), c); // and back
let w: WireColor = (&c).into(); // …and reusable in-program
assert_eq!(w.r, 0x11);Use #[bin(try_wire = WireType)] for a fallible decode (TryFrom<WireType> for Self, whose
Error: Display); a conversion error becomes a decode error
(ErrorKind::Convert) — the parser never panics.
§The closure form
When the mapping is small and you don’t need a reusable From, put it inline:
use bnb::bin;
#[bin(big)]
#[derive(Debug, Clone, PartialEq)]
struct WireTemp { biased: u8 } // stored as celsius + 40
#[bin(
map = |w: WireTemp| Celsius(w.biased as i16 - 40),
bw_map = |c: &Celsius| WireTemp { biased: (c.0 + 40) as u8 }
)]
#[derive(Debug, Clone, PartialEq)]
struct Celsius(i16);
assert_eq!(Celsius(10).to_bytes().unwrap(), [50]);
assert_eq!(Celsius::decode_exact(&[50]).unwrap(), Celsius(10));try_map = |w: Wire| Result<Self, E> is the fallible closure form (the dual of try_wire).
§Variable-length logical formats
Because a mapped type does not auto-implement FixedBitLen, its
wire form may be variable-length — a count-driven Vec, a length-prefixed string, anything:
use bnb::bin;
#[bin(big)]
#[derive(Debug, Clone, PartialEq)]
struct WireText { n: u8, #[br(count = n)] data: Vec<u8> } // variable-length
#[bin(wire = WireText)]
#[derive(Debug, Clone, PartialEq)]
struct Text(String);
impl From<WireText> for Text {
fn from(w: WireText) -> Self { Text(String::from_utf8_lossy(&w.data).into_owned()) }
}
impl From<&Text> for WireText {
fn from(t: &Text) -> Self { WireText { n: t.0.len() as u8, data: t.0.as_bytes().to_vec() } }
}
let t = Text("hi".into());
assert_eq!(t.to_bytes().unwrap(), [2, b'h', b'i']);
assert_eq!(Text::decode_exact(&[3, b'a', b'b', b'c']).unwrap(), Text("abc".into()));§Generated surface and nesting
A mapped type gets the same entry points as any #[bin] message — decode, decode_exact,
decode_all, decode_iter, peek, to_bytes — plus the BitDecode /
BitEncode impls. It nests as a field via a count/ctx/if directive
like any message; to nest a fixed-wire mapped type as a plain field, add a one-line
FixedBitLen impl forwarding the wire type’s size (it only compiles if
the wire really is fixed-length):
use bnb::bin;
impl bnb::FixedBitLen for Color {
const BIT_LEN: u32 = <WireColor as bnb::FixedBitLen>::BIT_LEN;
}
#[bin(big)]
#[derive(Debug, PartialEq)]
struct Pixel { color: Color, alpha: u8 }
let px = Pixel { color: Color { rgb: 0x01_02_03 }, alpha: 0xFF };
assert_eq!(px.to_bytes().unwrap(), [0x01, 0x02, 0x03, 0xFF]);
assert_eq!(Pixel::decode_exact(&[0x01, 0x02, 0x03, 0xFF]).unwrap(), px);§Notes
- One direction is fine. For the closure form, give only
map/try_map(decode-only) or onlybw_map(encode-only). For the conversion-trait form, addread_only/write_only. - Byte/bit order comes from the wire type, so
big/little/bitson the mapped struct itself don’t apply, andmagic/ctx/validatebelong on the wire type. - Pick a form: reach for
wire/try_wirewhen you want the transitions in a named, reusable place or the wire form is variable-length; reach formap/bw_mapfor a quick inline fixed mapping.