Skip to main content

bnb/guide/
mapping.rs

1//! Whole-struct wire mapping — a *logical* type that serializes via a separate *wire* type.
2//!
3//! [`directives`](super::directives) showed `#[br(map)]`/`#[bw(map)]` transforming one *field*
4//! between its wire value and a friendlier type. The same idea applies to a **whole struct**: a
5//! *logical* type whose on-the-wire form is a different *wire* type. The struct's own fields are
6//! the logical data — never read or written directly; the wire type owns the bytes.
7//!
8//! There are two forms:
9//!
10//! - **Conversion-trait form** — `#[bin(wire = WireType)]`, driven by standard `From`/`TryFrom`
11//!   impls you write. The transitions live in named `impl` blocks — a clean home, reusable
12//!   anywhere in your program — and the **wire type may be variable-length**.
13//! - **Closure form** — `#[bin(map = …, bw_map = …)]`, with the transitions inline in the
14//!   attribute. Quick for a small fixed mapping.
15//!
16//! # The conversion-trait form
17//!
18//! `#[bin(wire = WireType)]` needs `From<WireType> for Self` (decode) and `From<&Self> for
19//! WireType` (encode). Decode reads `WireType` then `Self::from(wire)`; encode does
20//! `WireType::from(&self)` then writes.
21//!
22//! ```
23//! use bnb::bin;
24//!
25//! // The wire form: a normal #[bin] message.
26//! #[bin(big)]
27//! #[derive(Debug, Clone, PartialEq)]
28//! struct WireColor { r: u8, g: u8, b: u8 }
29//!
30//! // The logical form: a packed u32, mapped to/from WireColor.
31//! #[bin(wire = WireColor)]
32//! #[derive(Debug, Clone, PartialEq)]
33//! struct Color { rgb: u32 }
34//!
35//! impl From<WireColor> for Color {
36//!     fn from(w: WireColor) -> Self {
37//!         Color { rgb: (w.r as u32) << 16 | (w.g as u32) << 8 | w.b as u32 }
38//!     }
39//! }
40//! impl From<&Color> for WireColor {
41//!     fn from(c: &Color) -> Self {
42//!         WireColor { r: (c.rgb >> 16) as u8, g: (c.rgb >> 8) as u8, b: c.rgb as u8 }
43//!     }
44//! }
45//!
46//! let c = Color { rgb: 0x11_22_33 };
47//! assert_eq!(c.to_bytes().unwrap(), [0x11, 0x22, 0x33]);            // mapped onto the wire
48//! assert_eq!(Color::decode_exact(&[0x11, 0x22, 0x33]).unwrap(), c); // and back
49//! let w: WireColor = (&c).into();                                  // …and reusable in-program
50//! assert_eq!(w.r, 0x11);
51//! ```
52//!
53//! Use `#[bin(try_wire = WireType)]` for a fallible decode (`TryFrom<WireType> for Self`, whose
54//! `Error: Display`); a conversion error becomes a decode error
55//! ([`ErrorKind::Convert`](crate::ErrorKind)) — the parser never panics.
56//!
57//! # The closure form
58//!
59//! When the mapping is small and you don't need a reusable `From`, put it inline:
60//!
61//! ```
62//! use bnb::bin;
63//!
64//! #[bin(big)]
65//! #[derive(Debug, Clone, PartialEq)]
66//! struct WireTemp { biased: u8 } // stored as celsius + 40
67//!
68//! #[bin(
69//!     map = |w: WireTemp| Celsius(w.biased as i16 - 40),
70//!     bw_map = |c: &Celsius| WireTemp { biased: (c.0 + 40) as u8 }
71//! )]
72//! #[derive(Debug, Clone, PartialEq)]
73//! struct Celsius(i16);
74//!
75//! assert_eq!(Celsius(10).to_bytes().unwrap(), [50]);
76//! assert_eq!(Celsius::decode_exact(&[50]).unwrap(), Celsius(10));
77//! ```
78//!
79//! `try_map = |w: Wire| Result<Self, E>` is the fallible closure form (the dual of `try_wire`).
80//!
81//! # Variable-length logical formats
82//!
83//! Because a mapped type does **not** auto-implement [`FixedBitLen`](crate::FixedBitLen), its
84//! wire form may be variable-length — a count-driven `Vec`, a length-prefixed string, anything:
85//!
86//! ```
87//! use bnb::bin;
88//!
89//! #[bin(big)]
90//! #[derive(Debug, Clone, PartialEq)]
91//! struct WireText { n: u8, #[br(count = n)] data: Vec<u8> } // variable-length
92//!
93//! #[bin(wire = WireText)]
94//! #[derive(Debug, Clone, PartialEq)]
95//! struct Text(String);
96//! impl From<WireText> for Text {
97//!     fn from(w: WireText) -> Self { Text(String::from_utf8_lossy(&w.data).into_owned()) }
98//! }
99//! impl From<&Text> for WireText {
100//!     fn from(t: &Text) -> Self { WireText { n: t.0.len() as u8, data: t.0.as_bytes().to_vec() } }
101//! }
102//!
103//! let t = Text("hi".into());
104//! assert_eq!(t.to_bytes().unwrap(), [2, b'h', b'i']);
105//! assert_eq!(Text::decode_exact(&[3, b'a', b'b', b'c']).unwrap(), Text("abc".into()));
106//! ```
107//!
108//! # Generated surface and nesting
109//!
110//! A mapped type gets the same entry points as any `#[bin]` message — `decode`, `decode_exact`,
111//! `decode_all`, `decode_iter`, `peek`, `to_bytes` — plus the [`BitDecode`](crate::BitDecode) /
112//! [`BitEncode`](crate::BitEncode) impls. It nests as a field via a `count`/`ctx`/`if` directive
113//! like any message; to nest a **fixed-wire** mapped type as a *plain* field, add a one-line
114//! [`FixedBitLen`](crate::FixedBitLen) impl forwarding the wire type's size (it only compiles if
115//! the wire really is fixed-length):
116//!
117//! ```
118//! use bnb::bin;
119//! # #[bin(big)] #[derive(Debug, Clone, PartialEq)] struct WireColor { r: u8, g: u8, b: u8 }
120//! # #[bin(wire = WireColor)] #[derive(Debug, Clone, PartialEq)] struct Color { rgb: u32 }
121//! # 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 } } }
122//! # 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 } } }
123//! impl bnb::FixedBitLen for Color {
124//!     const BIT_LEN: u32 = <WireColor as bnb::FixedBitLen>::BIT_LEN;
125//! }
126//!
127//! #[bin(big)]
128//! #[derive(Debug, PartialEq)]
129//! struct Pixel { color: Color, alpha: u8 }
130//!
131//! let px = Pixel { color: Color { rgb: 0x01_02_03 }, alpha: 0xFF };
132//! assert_eq!(px.to_bytes().unwrap(), [0x01, 0x02, 0x03, 0xFF]);
133//! assert_eq!(Pixel::decode_exact(&[0x01, 0x02, 0x03, 0xFF]).unwrap(), px);
134//! ```
135//!
136//! # Notes
137//!
138//! - **One direction is fine.** For the closure form, give only `map`/`try_map` (decode-only) or
139//!   only `bw_map` (encode-only). For the conversion-trait form, add `read_only`/`write_only`.
140//! - **Byte/bit order comes from the wire type**, so `big`/`little`/`bits` on the mapped
141//!   struct itself don't apply, and `magic`/`ctx`/`validate` belong on the wire type.
142//! - **Pick a form:** reach for `wire`/`try_wire` when you want the transitions in a named,
143//!   reusable place or the wire form is variable-length; reach for `map`/`bw_map` for a quick
144//!   inline fixed mapping.