numeric_enum_macro/
lib.rs

1//! A declarative macro for type-safe enum-to-numbers conversion. `no-std` supported!
2//!
3//! ```
4//! use numeric_enum_macro::numeric_enum;
5//!
6//! numeric_enum! {
7//!     #[repr(i64)] // repr must go first.
8//!     /// Some docs.
9//!     ///
10//!     /// Multiline docs works too.
11//!     #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] // all the attributes are forwarded!
12//!     pub enum Lol {
13//!         // All the constants must have explicit values assigned!
14//!         Kek = 14,
15//!         Wow = 87,
16//!     }
17//! }
18//!
19//! const KEK: u32 = 0;
20//! const WOW: u32 = 1;
21//!
22//! numeric_enum! {
23//!     #[repr(u32)] // repr must go first.
24//!     /// Some docs.
25//!     ///
26//!     /// Multiline docs works too.
27//!     #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] // all the attributes are forwarded!
28//!     pub enum Lol2 {
29//!         /// This is KEK
30//!         Kek = KEK,
31//!         /// And this is WOW
32//!         Wow = WOW,
33//!     }
34//! }
35//!
36//! # use ::core::convert::TryFrom;
37//! // Conversion to raw number:
38//! assert_eq!(14i64, Lol::Kek.into());
39//! // Conversion from raw number:
40//! assert_eq!(Ok(Lol::Wow), Lol::try_from(87));
41//! // Unknown number:
42//! assert_eq!(Err(88), Lol::try_from(88));
43//!
44//! assert_eq!(Ok(Lol2::Wow), Lol2::try_from(WOW));
45//! ```
46
47#![no_std]
48
49/// Declares an enum with a given numeric representation defined by literals.
50///
51/// Only explicitly enumerated enum constants are supported.
52///
53/// Automatically derives `TryFrom<$repr>` and `From<$name>`.
54///
55/// For examples look at the crate root documentation.
56#[macro_export]
57macro_rules! numeric_enum {
58    (#[repr($repr:ident)]
59     $(#$attrs:tt)* $vis:vis enum $name:ident {
60        $($(#$enum_attrs:tt)* $enum:ident = $constant:expr),* $(,)?
61    } ) => {
62        #[repr($repr)]
63        $(#$attrs)*
64        $vis enum $name {
65            $($(#$enum_attrs)* $enum = $constant),*
66        }
67
68        impl ::core::convert::TryFrom<$repr> for $name {
69            type Error = $repr;
70
71            fn try_from(value: $repr) -> ::core::result::Result<Self, $repr> {
72                $(if $constant == value { return Ok($name :: $enum); } )*
73                Err(value)
74            }
75        }
76
77        impl ::core::convert::From<$name> for $repr {
78            fn from(value: $name) -> $repr {
79                match value {
80                    $($name :: $enum => $constant,)*
81                }
82            }
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    numeric_enum! {
92        #[repr(i16)]
93        /// Documentation.
94        ///
95        /// Multiline.
96        #[derive(Debug, PartialEq, Eq)]
97        pub enum PublicEnum { Zero = 0, Lol = -1 }
98    }
99
100    numeric_enum! {
101        #[repr(u8)]
102        enum TrailingComa { A = 0, B = 1, }
103    }
104
105    numeric_enum! {
106        #[repr(u8)]
107        enum NoTrailingComa { A = 0, B = 1 }
108    }
109
110    const ZERO: u8 = 0;
111    const LOL: u8 = 1;
112
113    numeric_enum! {
114        #[repr(u8)]
115        enum PrivateEnum {
116            Zero = ZERO,
117            Lol = LOL,
118        }
119    }
120
121    #[test]
122    fn it_works() {
123        use core::convert::TryFrom;
124
125        assert_eq!(-1i16, PublicEnum::Lol.into());
126        assert_eq!(PublicEnum::try_from(0), Ok(PublicEnum::Zero));
127        assert_eq!(PublicEnum::try_from(-1), Ok(PublicEnum::Lol));
128        assert_eq!(PublicEnum::try_from(2), Err(2));
129    }
130}