Skip to main content

bnb/guide/
enums.rs

1//! `#[derive(BitEnum)]` — map an enum to a fixed-width integer discriminant.
2//!
3//! ```
4//! use bnb::{BitEnum, Bits, u4};
5//!
6//! #[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
7//! #[bit_enum(u4)]
8//! enum Op {
9//!     Read,    // 0 (auto-numbered from 0)
10//!     Write,   // 1
11//!     Erase,   // 2
12//!     #[catch_all]
13//!     Other(u4),
14//! }
15//!
16//! assert_eq!(Op::Write.into_bits(), 1);
17//! assert_eq!(Op::from_bits(2), Op::Erase);
18//! ```
19//!
20//! `#[bit_enum(uN)]` sets the width. Unit variants take auto-incrementing
21//! discriminants from 0, or you can pin them with `= N`:
22//!
23//! ```
24//! use bnb::Bits;
25//!
26//! #[derive(bnb::BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
27//! #[bit_enum(u8)]
28//! #[repr(u8)]
29//! enum EtherType {
30//!     Ipv4 = 0x00,
31//!     Arp = 0x06,
32//!     #[catch_all]
33//!     Other(u8),
34//! }
35//! assert_eq!(EtherType::Arp.into_bits(), 0x06);
36//! ```
37//!
38//! (Rust forbids explicit discriminants on an enum that also has a tuple variant
39//! unless it carries `#[repr(..)]`; for contiguous-from-0 values, drop the `= N` and
40//! the `#[repr]`.)
41//!
42//! # Catch-all: lossless, dual-use decoding
43//!
44//! A single `#[catch_all]` tuple variant (holding the width type) captures any value
45//! the named variants don't, so decoding is **total and lossless** — the parser never
46//! rejects representable input, and the original value round-trips:
47//!
48//! ```
49//! use bnb::{BitEnum, Bits, u4};
50//! # #[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
51//! # #[bit_enum(u4)]
52//! # enum Op { Read, Write, Erase, #[catch_all] Other(u4) }
53//! assert_eq!(Op::from_bits(9), Op::Other(u4::new(9))); // unknown — preserved
54//! assert_eq!(Op::Other(u4::new(9)).into_bits(), 9);     // round-trips exactly
55//! ```
56//!
57//! # No catch-all: exhaustive, or `closed`
58//!
59//! Without a catch-all, `from_bits` (the infallible decode path) has nowhere to put
60//! an unknown discriminant. So the derive requires one of two things:
61//!
62//! - the variants **cover the whole width** (then an unknown value is impossible), or
63//! - you mark the enum **`closed`** to assert it is a closed set on purpose.
64//!
65//! A fully-covered enum needs no marker:
66//!
67//! ```
68//! use bnb::{Bits, u2};
69//! #[derive(bnb::BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
70//! #[bit_enum(u2)]
71//! enum Quadrant { Ne, Nw, Sw, Se }   // all 4 values of a u2 named — exhaustive
72//! assert_eq!(Quadrant::from_bits(3), Quadrant::Se);
73//! ```
74//!
75//! An enum that is *not* exhaustive and has no catch-all is a **compile error** unless
76//! marked `closed` — the diagnostic points you at both fixes:
77//!
78//! ```compile_fail
79//! #[derive(bnb::BitEnum, Clone, Copy)]
80//! #[bit_enum(u8)]
81//! enum Bad { A = 1, B = 2 }   // error: not exhaustive, no #[catch_all], not `closed`
82//! # let _ = Bad::A;
83//! ```
84//!
85//! With `closed` it compiles; the checked `TryFrom` rejects unknowns, while the
86//! infallible `from_bits` **panics** on an out-of-set value (a declared contract
87//! violation), so only use `closed` for sets you never decode from untrusted bytes:
88//!
89//! ```should_panic
90//! use bnb::Bits;
91//! #[derive(bnb::BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
92//! #[bit_enum(u8, closed)]
93//! #[repr(u8)]
94//! enum Direction { Request = 1, Reply = 2 }
95//!
96//! assert_eq!(Direction::try_from(2u8), Ok(Direction::Reply)); // checked: fine
97//! assert!(Direction::try_from(7u8).is_err());                 // checked: rejected
98//! let _ = Direction::from_bits(7);                            // infallible: panics
99//! ```
100//!
101//! # `num_enum` parity for primitive-width enums
102//!
103//! When the width is a primitive (`u8`/`u16`/`u32`/`u64`/`u128`), the derive also emits the
104//! `num_enum`-style primitive conversions — so a magic-byte enum needs no hand-written
105//! `From` impl or round-trip test:
106//!
107//! - `From<Enum> for uN` — always;
108//! - with a catch-all, `From<uN> for Enum` — total (unknowns absorbed);
109//! - without one (a `closed` enum), `TryFrom<uN> for Enum` — checked, erroring with
110//!   [`UnknownDiscriminant`](crate::UnknownDiscriminant).
111//!
112//! ```
113//! #[derive(bnb::BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
114//! #[bit_enum(u8)] #[repr(u8)]
115//! enum EtherType { Ipv4 = 0x00, Arp = 0x06, #[catch_all] Other(u8) }
116//!
117//! assert_eq!(u8::from(EtherType::Arp), 0x06);             // enum -> primitive
118//! assert_eq!(EtherType::from(0x06u8), EtherType::Arp);    // total (has catch-all)
119//! assert_eq!(EtherType::from(0x99u8), EtherType::Other(0x99));
120//! ```
121//!
122//! A non-primitive-width enum — a sub-byte `u4`, or even a byte-aligned but non-primitive
123//! `u24` — gets only the [`Bits`](crate::Bits)/[`BitEnum`](crate::BitEnum) impls: it is
124//! meaningful only nested in a [`#[bitfield]`](super::bitfields) or a `#[bin]` message,
125//! where its bits are placed in context.