bijective_enum_map/bijective.rs
1/// Map an enum into and from another type (or two types) using `From` in each direction.
2///
3/// The enum type must be specified, followed by the type to map the enum into (`$into`),
4/// optionally followed by the type to map into the enum (`$from`).
5/// If `$from` is not specified, it is set to `$into`.
6///
7/// The two types must be similar enough that the same value expression (e.g., a numeric or string
8/// literal) works for either; moreover, the expression must also be a valid pattern for a match
9/// arm, so arbitrarily complicated expressions are not permitted.
10/// In practice, the types should usually be the same, but specifying two different types could be
11/// used for converting into a static reference and trying to convert from a reference of
12/// unspecified lifetime, for example. (Such usage is more useful in `injective_enum_map`.)
13/// Unintended options like mapping into `Range<u8>` and from `u8` might be possible, but are
14/// not tested here. Note that non-unit variants are supported.
15///
16/// This map is intended to be "bijective", which means that it is both "surjective" and
17/// "injective". Being surjective means that every value of the target type to map the enum into
18/// should be associated with some enum variant. This is enforced by a `match` mapping values into
19/// enum variants; if your value expressions/patterns are fairly normal
20/// (no tricks with `|`, `..`, and the like), surjectivity will hold unless you repeat enum
21/// variants (causing some arms to be unreachable when mapping variants to values).
22///
23/// Being injective means that different enum variants should map into different
24/// values, so that they can be mapped back unambiguously.
25///
26/// If the map is not bijective, and either some enum variants are repeated or multiple enum
27/// variants map to the same value, then a compiler warning from `#[warn(unreachable_patterns)]`
28/// *should* be printed in most circumstances, but it could be a silent logic error. In such a
29/// case, only the first duplicate arm (in each direction) will be taken for the duplicated variant
30/// or value.
31///
32/// # Examples
33///
34/// ## Map into and from two other types:
35/// ```
36/// use bijective_enum_map::bijective_enum_map;
37/// #[derive(Debug, PartialEq, Eq)]
38/// enum AtMostTwo {
39/// Zero,
40/// One,
41/// Two,
42/// }
43///
44/// #[derive(Debug, PartialEq, Eq)]
45/// enum Other {
46/// Zeroeth,
47/// First,
48/// Second,
49/// }
50///
51/// bijective_enum_map! {
52/// AtMostTwo, Option<bool>,
53/// Zero <=> Some(false),
54/// One <=> Some(true),
55/// Two <=> None,
56/// }
57///
58/// // You can specify the same type twice for from/into, it has no effect.
59/// // Note that the path to other enums does have to be specified.
60/// bijective_enum_map! {
61/// AtMostTwo, Other, Other,
62/// Zero <=> Other::Zeroeth,
63/// One <=> Other::First,
64/// Two <=> Other::Second,
65/// }
66///
67/// // The compiler can infer that this is `Option::<bool>::from`
68/// assert_eq!(Option::from(AtMostTwo::One), Some(true));
69/// assert_eq!(AtMostTwo::from(None), AtMostTwo::Two);
70/// assert_eq!(AtMostTwo::from(Other::Zeroeth), AtMostTwo::Zero);
71/// ```
72///
73/// ## Map with a non-unit variant:
74/// ```
75/// use bijective_enum_map::bijective_enum_map;
76/// #[derive(Debug, PartialEq, Eq)]
77/// enum MaybeData {
78/// Data(String),
79/// Nothing,
80/// }
81///
82/// bijective_enum_map! {
83/// MaybeData, Option<String>,
84/// Data(data) <=> Some(data),
85/// Nothing <=> None,
86/// }
87///
88/// assert_eq!(
89/// MaybeData::from(Some("What do you get when you multiply six by nine?".to_owned())),
90/// MaybeData::Data("What do you get when you multiply six by nine?".to_owned()),
91/// );
92/// assert_eq!(
93/// Option::from(MaybeData::Data("42".to_owned())),
94/// Some("42".to_owned()),
95/// );
96/// ```
97///
98/// ## Intentionally violate injectivity:
99/// ```
100/// use bijective_enum_map::bijective_enum_map;
101/// #[derive(Debug, PartialEq, Eq)]
102/// enum ParsedString {
103/// Quoted(String),
104/// Unquoted(String),
105/// Unknown(String),
106/// }
107///
108/// // Slightly extreme option to silence warnings; you should probably run your code without
109/// // a similar allow attribute in order to check that the warning which occurs is as expected.
110/// // It seems that `#[allow(unreachable_patterns)]` is overridden by
111/// // the `#[warn(unreachable_patterns)]` inside the macro.
112/// #[allow(warnings)]
113/// {
114/// bijective_enum_map! {
115/// ParsedString, String,
116/// // Because injectivity is violated, the order is significant.
117/// // With this order, all incoming strings are mapped to the `Unknown` variant.
118/// Unknown(string) <=> string,
119/// Quoted(string) <=> string,
120/// Unquoted(string) <=> string,
121/// }
122/// }
123///
124/// assert_eq!(
125/// String::from(ParsedString::Quoted("string".to_owned())),
126/// "string".to_owned(),
127/// );
128/// assert_eq!(
129/// String::from(ParsedString::Unquoted("string".to_owned())),
130/// "string".to_owned(),
131/// );
132/// assert_eq!(
133/// ParsedString::from("string".to_owned()),
134/// ParsedString::Unknown("string".to_owned()),
135/// );
136/// ```
137///
138/// ## Empty enum, mapped into and from another type:
139/// ```
140/// use bijective_enum_map::bijective_enum_map;
141/// #[derive(Debug, PartialEq, Eq)]
142/// enum Empty {}
143/// enum AnotherEmpty {}
144///
145/// // The trailing comma is always optional
146/// bijective_enum_map! { Empty, AnotherEmpty }
147///
148/// // The below is to confirm that the appropriate `From` implementations exist
149/// fn _new_empty() -> Empty {
150/// panic!()
151/// }
152/// fn _new_another_empty() -> AnotherEmpty {
153/// AnotherEmpty::from(_new_empty())
154/// }
155/// fn _round_trip() -> Empty {
156/// Empty::from(_new_another_empty())
157/// }
158/// ```
159#[macro_export]
160macro_rules! bijective_enum_map {
161 { $enum_ty:ty, $into:ty, $from:ty, $($body:tt)* } => {
162 $crate::__impl_from_enum! { $enum_ty, $into, $($body)* }
163 $crate::__impl_enum_from! { $enum_ty, $from, $($body)* }
164 };
165
166 { $enum_ty:ty, $into:ty, $from:ty } => {
167 $crate::__impl_from_enum! { $enum_ty, $into }
168 $crate::__impl_enum_from! { $enum_ty, $from }
169 };
170
171 { $enum_ty:ty, $both:ty, $($body:tt)* } => {
172 $crate::__impl_from_enum! { $enum_ty, $both, $($body)* }
173 $crate::__impl_enum_from! { $enum_ty, $both, $($body)* }
174 };
175
176 { $enum_ty:ty, $both:ty } => {
177 $crate::__impl_from_enum! { $enum_ty, $both }
178 $crate::__impl_enum_from! { $enum_ty, $both }
179 };
180}
181
182
183#[cfg(test)]
184mod tests {
185 use crate::bijective_enum_map;
186
187 #[test]
188 fn empty_both_specified() {
189 #[derive(Debug, PartialEq, Eq)]
190 enum Empty {}
191 enum AnotherEmpty {}
192
193 // The trailing comma is always optional
194 bijective_enum_map! { Empty, AnotherEmpty, AnotherEmpty }
195
196 // The below is to confirm that the appropriate `From` implementations exist
197 fn _new_empty() -> Empty {
198 panic!()
199 }
200 fn _new_another_empty() -> AnotherEmpty {
201 AnotherEmpty::from(_new_empty())
202 }
203 fn _round_trip() -> Empty {
204 Empty::from(_new_another_empty())
205 }
206 }
207
208 #[test]
209 fn empty_one_specified() {
210 #[derive(Debug, PartialEq, Eq)]
211 enum Empty {}
212 enum AnotherEmpty {}
213
214 // The trailing comma is always optional
215 bijective_enum_map! { Empty, AnotherEmpty, }
216
217 // The below is to confirm that the appropriate `From` implementations exist
218 fn _new_empty() -> Empty {
219 panic!()
220 }
221 fn _new_another_empty() -> AnotherEmpty {
222 AnotherEmpty::from(_new_empty())
223 }
224 fn _round_trip() -> Empty {
225 Empty::from(_new_another_empty())
226 }
227 }
228
229 #[test]
230 fn nonempty_both_specified() {
231 #[derive(Debug, PartialEq, Eq)]
232 enum Trivial {
233 Num(u8),
234 }
235
236 bijective_enum_map! {Trivial, u8, u8, Num(num) <=> num}
237
238 assert_eq!(Trivial::from(2_u8), Trivial::Num(2));
239 assert_eq!(u8::from(Trivial::Num(3)), 3);
240 }
241
242 #[test]
243 fn nonempty_one_specified() {
244 #[derive(Debug, PartialEq, Eq)]
245 enum Trivial {
246 Num(u8),
247 }
248
249 bijective_enum_map! {Trivial, u8, Num(num) <=> num}
250
251 assert_eq!(Trivial::from(2_u8), Trivial::Num(2));
252 assert_eq!(u8::from(Trivial::Num(3)), 3);
253 }
254
255 #[test]
256 fn nonempty_enums() {
257 #[derive(Debug, PartialEq, Eq)]
258 enum Enum {
259 One,
260 Two,
261 Three,
262 }
263
264 #[derive(Debug, PartialEq, Eq)]
265 enum Other {
266 Uno,
267 Dos,
268 Tres,
269 }
270
271 bijective_enum_map! {
272 Enum, Other, Other,
273 One <=> Other::Uno,
274 Two <=> Other::Dos,
275 Three <=> Other::Tres,
276 }
277
278 assert_eq!(Other::from(Enum::Three), Other::Tres);
279 assert_eq!(Enum::from(Other::Uno), Enum::One);
280 }
281
282 #[test]
283 fn trailing_commas() {
284 enum Empty {}
285 enum AnotherEmpty {}
286 enum YetAnotherEmpty {}
287 enum AFourthEmpty {}
288
289 enum Trivial {
290 Num(u8),
291 }
292 enum Trivial2 {
293 Num(u16),
294 }
295 enum Trivial3 {
296 Num(i8),
297 }
298 enum Trivial4 {
299 Num(i16),
300 }
301
302 bijective_enum_map!(Empty, AnotherEmpty, AnotherEmpty);
303 bijective_enum_map! { Empty, YetAnotherEmpty };
304 bijective_enum_map! {
305 AnotherEmpty, YetAnotherEmpty, YetAnotherEmpty,
306 };
307 bijective_enum_map! { Empty, AFourthEmpty, };
308
309 bijective_enum_map!(Trivial, u8, u8, Num(num) <=> num);
310 bijective_enum_map! { Trivial2, u16, Num(num) <=> num };
311 bijective_enum_map! {
312 Trivial3, i8, i8, Num(num) <=> num,
313 };
314 bijective_enum_map! { Trivial4, i16, Num(num) <=> num, };
315 }
316
317 #[test]
318 fn non_unit_variant() {
319 // These would be strings, but don't want to pull in `alloc`.
320 #[derive(Debug, PartialEq)]
321 enum Thing {
322 Player {
323 name: &'static str,
324 hp: u32,
325 strength: f32,
326 },
327 PhysicalObject {
328 name: &'static str,
329 hp: u32,
330 },
331 Spell {
332 name: &'static str,
333 strength: f32,
334 },
335 Marker(&'static str),
336 HardcodedMarker(&'static str, u32),
337 Unknown(Stuff),
338 }
339
340 type Stuff = (u8, Option<&'static str>, Option<u32>, Option<f32>);
341
342 bijective_enum_map! {
343 Thing, Stuff,
344 Player { name, hp, strength } <=> (0, Some(name), Some(hp), Some(strength)),
345 PhysicalObject { name, hp } <=> (1, Some(name), Some(hp), None),
346 Spell { name, strength } <=> (2, Some(name), None, Some(strength)),
347 Marker(name) <=> (3, Some(name), None, None),
348 HardcodedMarker(name, id) <=> (4, Some(name), Some(id), None),
349 Unknown(stuff) <=> stuff,
350 }
351
352 assert_eq!(
353 Stuff::from(Thing::Player { name: "person", hp: 2, strength: 1.5 }),
354 (0, Some("person"), Some(2), Some(1.5)),
355 );
356 assert_eq!(
357 Stuff::from(Thing::Marker("place")),
358 (3, Some("place"), None, None),
359 );
360 assert_eq!(
361 Thing::from((1_u8, Some("object"), Some(100_u32), None)),
362 Thing::PhysicalObject { name: "object", hp: 100 },
363 );
364 assert_eq!(
365 Thing::from((1_u8, Some("object"), Some(100_u32), Some(1e30))),
366 Thing::Unknown((1_u8, Some("object"), Some(100_u32), Some(1e30))),
367 );
368 }
369
370 #[test]
371 fn intentionally_non_surjective() {
372 #[derive(Debug, PartialEq, Eq)]
373 enum Enum {
374 One,
375 Two,
376 Three,
377 }
378
379 #[derive(Debug, PartialEq, Eq)]
380 enum Other {
381 Uno,
382 Dos,
383 Tres,
384 Cuatro,
385 }
386
387 #[allow(warnings)]
388 {
389 bijective_enum_map! {
390 Enum, Other, Other,
391 One <=> Other::Uno,
392 Two <=> Other::Dos,
393 Three <=> Other::Tres,
394 Three <=> Other::Cuatro,
395 }
396 }
397
398 assert_eq!(Other::from(Enum::Three), Other::Tres);
399 assert_eq!(Enum::from(Other::Uno), Enum::One);
400 assert_eq!(Enum::from(Other::Cuatro), Enum::Three);
401 }
402
403 #[test]
404 fn intentionally_non_injective() {
405 #[derive(Debug, PartialEq, Eq)]
406 enum Enum {
407 One,
408 Two,
409 Three,
410 }
411
412 #[derive(Debug, PartialEq, Eq)]
413 enum Other {
414 Uno,
415 Dos,
416 Tres,
417 Cuatro,
418 }
419
420 #[allow(warnings)]
421 {
422 bijective_enum_map! {
423 Other, Enum,
424 Uno <=> Enum::One,
425 Dos <=> Enum::Two,
426 Tres <=> Enum::Three,
427 Cuatro <=> Enum::Three,
428 }
429 }
430
431 assert_eq!(Other::from(Enum::Three), Other::Tres);
432 assert_eq!(Enum::from(Other::Uno), Enum::One);
433 assert_eq!(Enum::from(Other::Cuatro), Enum::Three);
434 }
435}
436
437#[cfg(doctest)]
438pub mod compile_fail_tests {
439 /// ```compile_fail,E0004
440 /// use bijective_enum_map::bijective_enum_map;
441 /// #[derive(Debug, PartialEq, Eq)]
442 /// enum Nonempty {
443 /// Something,
444 /// }
445 ///
446 /// bijective_enum_map! {Nonempty, u8}
447 /// ```
448 pub fn _nonempty_but_nothing_provided() {}
449
450 /// ```compile_fail,E0004
451 /// use bijective_enum_map::bijective_enum_map;
452 /// #[derive(Debug, PartialEq, Eq)]
453 /// enum Nonempty {
454 /// Something,
455 /// SomethingElse,
456 /// }
457 ///
458 /// bijective_enum_map! { Nonempty, u8, Something <=> 0 }
459 /// ```
460 pub fn _nonempty_but_not_enough_provided() {}
461
462 /// ```compile_fail
463 /// #![deny(warnings)]
464 ///
465 /// use bijective_enum_map::bijective_enum_map;
466 /// #[derive(Debug, PartialEq, Eq)]
467 /// enum AtMostTwo {
468 /// Zero,
469 /// One,
470 /// Two,
471 /// }
472 ///
473 /// bijective_enum_map! {
474 /// AtMostTwo, bool,
475 /// Zero <=> false,
476 /// One <=> true,
477 /// Two <=> false,
478 /// }
479 /// ```
480 pub fn _nonempty_not_injective_warning() {}
481
482 /// ```compile_fail
483 /// #![deny(warnings)]
484 ///
485 /// use bijective_enum_map::bijective_enum_map;
486 /// enum AtMostTwo {
487 /// Zero,
488 /// One,
489 /// Two,
490 /// }
491 ///
492 /// enum BoolEnum {
493 /// True,
494 /// False,
495 /// }
496 ///
497 /// bijective_enum_map! {
498 /// bool, AtMostTwo,
499 /// False <=> AtMostTwo::Zero,
500 /// True <=> AtMostTwo::One,
501 /// False <=> AtMostTwo::Two,
502 /// }
503 /// ```
504 pub fn _nonempty_not_surjective_warning() {}
505
506 /// ```compile_fail
507 /// #![deny(warnings)]
508 ///
509 /// use bijective_enum_map::bijective_enum_map;
510 /// #[derive(Debug, PartialEq, Eq)]
511 /// enum AtMostTwo {
512 /// Zero,
513 /// One,
514 /// Two,
515 /// }
516 ///
517 /// enum Other {
518 /// Uno,
519 /// Dos,
520 /// }
521 ///
522 /// bijective_enum_map! {
523 /// AtMostTwo, Other,
524 /// Zero <=> Other::Uno,
525 /// One <=> Other::Uno,
526 /// Two <=> Other::Dos,
527 /// }
528 ///
529 /// let _ = AtMostTwo::from(Other::Uno);
530 /// ```
531 pub fn _nonempty_to_enum_not_injective_warning() {}
532
533 // Doesn't seem to have a compiler error number
534 /// ```compile_fail
535 /// use bijective_enum_map::bijective_enum_map;
536 /// enum Nonempty {
537 /// Something,
538 /// }
539 ///
540 /// bijective_enum_map! {
541 /// Nonempty, ()
542 /// Something <=> ()
543 /// }
544 /// ```
545 pub fn _missing_comma() {}
546
547 /// ```
548 /// use bijective_enum_map::bijective_enum_map;
549 /// enum Nonempty {
550 /// Something,
551 /// }
552 ///
553 /// bijective_enum_map! {
554 /// Nonempty, (),
555 /// Something <=> ()
556 /// }
557 /// ```
558 pub fn not_missing_comma() {}
559}