utils/enum.rs
1//! Enum helpers.
2
3/// Macro to generate helpers for fieldless unit-only enums.
4///
5/// Helpers for accessing variants:
6/// - `COUNT`: The number of variants.
7/// - `ALL`: All variants (reference to a static array).
8/// - `iter()`: [`Iterator`] over all variants.
9///
10/// Helpers for converting to and from the discriminant (requires the enum to have an explicit
11/// `#[repr(...)]` attribute before all other attributes):
12/// - `checked_from_discriminant()` & `from_discriminant()`: Safe and panicking conversions from
13/// the discriminant.
14/// - [`From`] implementation from the enum to the discriminant.
15/// - [`TryFrom`] implementation from the discriminant to the enum.
16///
17/// Helpers for using variants as array indices (requires all variants to use implicit
18/// discriminants):
19/// - [`Index`](std::ops::Index) and [`IndexMut`](std::ops::IndexMut) implementations for
20/// `[T; COUNT]` arrays.
21///
22/// See also [`parser::parsable_enum!`](crate::parser::parsable_enum), which combined this macro
23/// with building a parser.
24///
25/// # Examples
26///
27/// Variant helpers:
28/// ```
29/// # use core::assert_matches;
30/// utils::enumerable_enum! {
31/// #[derive(Debug)]
32/// enum Direction {
33/// North,
34/// East,
35/// South,
36/// West,
37/// }
38/// }
39///
40/// assert_eq!(Direction::COUNT, 4);
41/// assert_matches!(Direction::ALL, &[
42/// Direction::North,
43/// Direction::East,
44/// Direction::South,
45/// Direction::West,
46/// ]);
47/// assert_matches!(Direction::iter().collect::<Vec<_>>().as_slice(), &[
48/// Direction::North,
49/// Direction::East,
50/// Direction::South,
51/// Direction::West,
52/// ]);
53/// ```
54///
55/// Discriminant helpers:
56/// ```
57/// utils::enumerable_enum! {
58/// #[repr(u8)]
59/// #[derive(Copy, Clone, Debug, Default, PartialEq)]
60/// enum Operation {
61/// Add,
62/// Sub,
63/// Mul,
64/// Div,
65/// #[default]
66/// Noop = 255,
67/// }
68/// }
69///
70/// assert_eq!(Operation::COUNT, 5);
71/// assert_eq!(Operation::ALL, &[
72/// Operation::Add,
73/// Operation::Sub,
74/// Operation::Mul,
75/// Operation::Div,
76/// Operation::Noop
77/// ]);
78/// assert_eq!(Operation::iter().collect::<Vec<_>>(), Operation::ALL);
79/// assert_eq!(Operation::from_discriminant(0), Operation::Add);
80/// assert_eq!(Operation::checked_from_discriminant(255), Some(Operation::Noop));
81/// assert_eq!(Operation::checked_from_discriminant(100), None);
82/// assert_eq!(u8::from(Operation::Add), 0u8);
83/// assert_eq!(Operation::try_from(2u8), Ok(Operation::Mul));
84/// assert_eq!(Operation::try_from(4u8), Err(()));
85/// ```
86///
87/// `from_discriminant` panics on invalid values:
88/// ```should_panic
89/// utils::enumerable_enum! {
90/// #[repr(u8)]
91/// enum Operation {
92/// Add,
93/// Sub,
94/// Mul,
95/// Div,
96/// }
97/// }
98///
99/// Operation::from_discriminant(64);
100/// ```
101///
102/// Index helpers:
103/// ```
104/// utils::enumerable_enum! {
105/// enum Register {
106/// A,
107/// B,
108/// C,
109/// D,
110/// }
111/// }
112///
113/// let mut registers = [0, 1, 2, 3];
114/// assert_eq!(registers[Register::A], 0);
115/// assert_eq!(registers[Register::B], 1);
116/// assert_eq!(registers[Register::C], 2);
117/// assert_eq!(registers[Register::D], 3);
118/// registers[Register::C] = 123;
119/// assert_eq!(registers[Register::C], 123);
120/// ```
121#[macro_export]
122macro_rules! enumerable_enum {
123 (
124 #[repr($t:ty)]
125 $(#[$meta:meta])*
126 $vis:vis enum $name:ident {
127 $($(#[$variant_meta:meta])* $variant:ident $(= $discriminant:expr)?),+ $(,)?
128 }
129 ) => {
130 // #[cfg(true) is used to force the second arm to be matched, even when no other attributes
131 // are provided.
132 $crate::enumerable_enum! {
133 $(#[$meta])*
134 #[cfg(true)]
135 #[repr($t)]
136 $vis enum $name {
137 $($(#[$variant_meta])* $variant $(= $discriminant)?,)+
138 }
139 }
140
141 impl $name {
142 /// Returns the variant with the provided discriminant, or [`None`] on invalid values.
143 #[inline]
144 #[must_use]
145 #[allow(non_snake_case, non_upper_case_globals)]
146 pub const fn checked_from_discriminant(v: $t) -> Option<Self> {
147 $(const $variant: $t = $name::$variant as $t;)+
148
149 match v {
150 $($variant => Some(Self::$variant),)+
151 _ => None,
152 }
153 }
154
155 /// Returns the variant with the provided discriminant, panicking on invalid values.
156 #[inline]
157 #[must_use]
158 pub const fn from_discriminant(v: $t) -> Self {
159 Self::checked_from_discriminant(v).expect("invalid discriminant")
160 }
161 }
162
163 impl From<$name> for $t {
164 #[inline]
165 fn from(v: $name) -> Self {
166 v as Self
167 }
168 }
169
170 impl TryFrom<$t> for $name {
171 type Error = ();
172
173 #[inline]
174 fn try_from(value: $t) -> Result<Self, ()> {
175 Self::checked_from_discriminant(value).ok_or(())
176 }
177 }
178 };
179 (
180 $(#[$meta:meta])*
181 $vis:vis enum $name:ident {
182 $($(#[$variant_meta:meta])* $variant:ident $(= $discriminant:expr)?),+ $(,)?
183 }
184 ) => {
185 $crate::enumerable_enum! {@enum
186 $(#[$meta])*
187 $vis enum $name {
188 $($(#[$variant_meta])* $variant $(= $discriminant)?,)+
189 }
190 }
191
192 impl $name {
193 /// The number of variants.
194 pub const COUNT: usize = [$(Self::$variant),+].len();
195 /// All variants.
196 pub const ALL: &'static [Self; Self::COUNT] = &[$(Self::$variant),+];
197
198 /// Iterator over all variants.
199 #[inline]
200 pub fn iter() -> ::std::array::IntoIter<Self, { $name::COUNT }> {
201 // Returns items by value without references even for non-copy types
202 [$(Self::$variant),+].into_iter()
203 }
204 }
205 };
206 (@enum
207 $(#[$meta:meta])*
208 $vis:vis enum $name:ident {
209 $($(#[$variant_meta:meta])* $variant:ident),+ $(,)?
210 }
211 ) => {
212 $(#[$meta])*
213 $vis enum $name {
214 $($(#[$variant_meta])* $variant,)+
215 }
216
217 impl<T> ::std::ops::Index<$name> for [T; $name::COUNT] {
218 type Output = T;
219
220 #[inline]
221 fn index(&self, index: $name) -> &Self::Output {
222 &self[index as usize]
223 }
224 }
225 impl<T> ::std::ops::IndexMut<$name> for [T; $name::COUNT] {
226 #[inline]
227 fn index_mut(&mut self, index: $name) -> &mut Self::Output {
228 &mut self[index as usize]
229 }
230 }
231 };
232 (@enum
233 $(#[$meta:meta])*
234 $vis:vis enum $name:ident {
235 $($(#[$variant_meta:meta])* $variant:ident $(= $discriminant:expr)?),+ $(,)?
236 }
237 ) => {
238 $(#[$meta])*
239 $vis enum $name {
240 $($(#[$variant_meta])* $variant $(= $discriminant)?,)+
241 }
242 };
243}