Skip to main content

enum_ordinalize/
lib.rs

1/*!
2# Enum Ordinalize
3
4This library enables enums to not only obtain the ordinal values of their variants but also allows for the construction of enums from an ordinal value.
5
6## Feature Flags
7
8The default features are `derive` and `traits`. The `derive` feature re-exports the `Ordinalize` derive macro, and the `traits` feature exposes the `Ordinalize` trait and enables the derive macro to implement it automatically.
9
10When only `derive` is enabled, the macro can still generate inherent constants and functions with the `#[ordinalize(...)]` attributes shown below, but it will not implement the `Ordinalize` trait.
11
12## Usage
13
14Use `#[derive(Ordinalize)]` to have an enum (which must only has unit variants) implement the `Ordinalize` trait.
15
16```rust
17# #[cfg(all(feature = "derive", feature = "traits"))]
18# {
19use enum_ordinalize::Ordinalize;
20
21#[derive(Debug, PartialEq, Eq, Ordinalize)]
22enum MyEnum {
23    Zero,
24    One,
25    Two,
26}
27
28assert_eq!(3, MyEnum::VARIANT_COUNT);
29assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two], MyEnum::VARIANTS);
30assert_eq!([0i8, 1i8, 2i8], MyEnum::VALUES);
31
32assert_eq!(0i8, MyEnum::Zero.ordinal());
33assert_eq!(1i8, MyEnum::One.ordinal());
34assert_eq!(2i8, MyEnum::Two.ordinal());
35
36assert_eq!(Some(MyEnum::Zero), MyEnum::from_ordinal(0i8));
37assert_eq!(Some(MyEnum::One), MyEnum::from_ordinal(1i8));
38assert_eq!(Some(MyEnum::Two), MyEnum::from_ordinal(2i8));
39
40assert_eq!(MyEnum::Zero, unsafe { MyEnum::from_ordinal_unsafe(0i8) });
41assert_eq!(MyEnum::One, unsafe { MyEnum::from_ordinal_unsafe(1i8) });
42assert_eq!(MyEnum::Two, unsafe { MyEnum::from_ordinal_unsafe(2i8) });
43# }
44```
45
46#### Quickly Implement `Serialize` and `Deserialize`
47
48The `Ordinalize` trait can be used to serialize an enum as its ordinal value and safely reject unknown values during deserialization. Add `serde` to your dependencies:
49
50```toml
51serde = { version = "1", default-features = false }
52```
53
54Then implement the traits using `ordinal` and `from_ordinal`:
55
56```rust,ignore
57use enum_ordinalize::Ordinalize;
58use serde::{Deserialize, Deserializer, Serialize, Serializer};
59
60#[derive(Debug, PartialEq, Eq, Ordinalize)]
61#[repr(i8)]
62enum MyEnum {
63    Zero,
64    One,
65    Two,
66}
67
68impl Serialize for MyEnum {
69    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70    where
71        S: Serializer,
72    {
73        serializer.serialize_i8(self.ordinal())
74    }
75}
76
77impl<'de> Deserialize<'de> for MyEnum {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: Deserializer<'de>,
81    {
82        let ordinal = i8::deserialize(deserializer)?;
83
84        Self::from_ordinal(ordinal).ok_or_else(|| {
85            <D::Error as serde::de::Error>::custom("invalid ordinal for MyEnum")
86        })
87    }
88}
89```
90
91#### The (Ordinal) Size of an Enum
92
93The ordinal value is an integer whose size is determined by the enum itself. The size of the enum increases with the magnitude of the variants' values, whether larger (or smaller if negative).
94
95For example,
96
97```rust
98# #[cfg(all(feature = "derive", feature = "traits"))]
99# {
100use enum_ordinalize::Ordinalize;
101
102#[derive(Debug, PartialEq, Eq, Ordinalize)]
103enum MyEnum {
104    Zero,
105    One,
106    Two,
107    Thousand = 1000,
108}
109
110assert_eq!(4, MyEnum::VARIANT_COUNT);
111assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two, MyEnum::Thousand], MyEnum::VARIANTS);
112assert_eq!([0i16, 1i16, 2i16, 1000i16], MyEnum::VALUES);
113
114assert_eq!(0i16, MyEnum::Zero.ordinal());
115assert_eq!(1i16, MyEnum::One.ordinal());
116assert_eq!(2i16, MyEnum::Two.ordinal());
117
118assert_eq!(Some(MyEnum::Zero), MyEnum::from_ordinal(0i16));
119assert_eq!(Some(MyEnum::One), MyEnum::from_ordinal(1i16));
120assert_eq!(Some(MyEnum::Two), MyEnum::from_ordinal(2i16));
121
122assert_eq!(MyEnum::Zero, unsafe { MyEnum::from_ordinal_unsafe(0i16) });
123assert_eq!(MyEnum::One, unsafe { MyEnum::from_ordinal_unsafe(1i16) });
124assert_eq!(MyEnum::Two, unsafe { MyEnum::from_ordinal_unsafe(2i16) });
125# }
126```
127
128In order to accommodate the value `1000`, the size of `MyEnum` increases. Consequently, the ordinal is represented in `i16` instead of `i8`.
129
130You can utilize the `#[repr(type)]` attribute to explicitly control the size. For instance,
131
132```rust
133# #[cfg(all(feature = "derive", feature = "traits"))]
134# {
135use enum_ordinalize::Ordinalize;
136
137#[derive(Debug, PartialEq, Eq, Ordinalize)]
138#[repr(usize)]
139enum MyEnum {
140    Zero,
141    One,
142    Two,
143    Thousand = 1000,
144}
145
146assert_eq!(4, MyEnum::VARIANT_COUNT);
147assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two, MyEnum::Thousand], MyEnum::VARIANTS);
148assert_eq!([0usize, 1usize, 2usize, 1000usize], MyEnum::VALUES);
149
150assert_eq!(0usize, MyEnum::Zero.ordinal());
151assert_eq!(1usize, MyEnum::One.ordinal());
152assert_eq!(2usize, MyEnum::Two.ordinal());
153
154assert_eq!(Some(MyEnum::Zero), MyEnum::from_ordinal(0usize));
155assert_eq!(Some(MyEnum::One), MyEnum::from_ordinal(1usize));
156assert_eq!(Some(MyEnum::Two), MyEnum::from_ordinal(2usize));
157
158assert_eq!(MyEnum::Zero, unsafe { MyEnum::from_ordinal_unsafe(0usize) });
159assert_eq!(MyEnum::One, unsafe { MyEnum::from_ordinal_unsafe(1usize) });
160assert_eq!(MyEnum::Two, unsafe { MyEnum::from_ordinal_unsafe(2usize) });
161# }
162```
163
164Path constants, casts, binary expressions, and const fn calls used as discriminants require an explicit integer `#[repr(...)]` because the derive macro cannot evaluate them while choosing the ordinal type.
165
166#### Useful Increment
167
168The integers represented by variants can be extended in successive increments and set explicitly from any value.
169
170```rust
171# #[cfg(all(feature = "derive", feature = "traits"))]
172# {
173use enum_ordinalize::Ordinalize;
174
175#[derive(Debug, PartialEq, Eq, Ordinalize)]
176enum MyEnum {
177    Two   = 2,
178    Three,
179    Four,
180    Eight = 8,
181    Nine,
182    NegativeTen = -10,
183    NegativeNine,
184}
185
186assert_eq!(7, MyEnum::VARIANT_COUNT);
187assert_eq!([MyEnum::Two, MyEnum::Three, MyEnum::Four, MyEnum::Eight, MyEnum::Nine, MyEnum::NegativeTen, MyEnum::NegativeNine], MyEnum::VARIANTS);
188assert_eq!([2i8, 3i8, 4i8, 8i8, 9i8, -10i8, -9i8], MyEnum::VALUES);
189
190assert_eq!(4i8, MyEnum::Four.ordinal());
191assert_eq!(9i8, MyEnum::Nine.ordinal());
192assert_eq!(-9i8, MyEnum::NegativeNine.ordinal());
193
194assert_eq!(Some(MyEnum::Four), MyEnum::from_ordinal(4i8));
195assert_eq!(Some(MyEnum::Nine), MyEnum::from_ordinal(9i8));
196assert_eq!(Some(MyEnum::NegativeNine), MyEnum::from_ordinal(-9i8));
197
198assert_eq!(MyEnum::Four, unsafe { MyEnum::from_ordinal_unsafe(4i8) });
199assert_eq!(MyEnum::Nine, unsafe { MyEnum::from_ordinal_unsafe(9i8) });
200assert_eq!(MyEnum::NegativeNine, unsafe { MyEnum::from_ordinal_unsafe(-9i8) });
201# }
202```
203
204#### Implement Functionality for an enum on Itself
205
206For some reason, if you don't want to implement the `Ordinalize` trait for your enum, you can choose to disable the trait implementation and enable the constants/functions one by one. Functions are `const fn`. Names and visibility can also be defined by you.
207
208```rust
209# #[cfg(feature = "derive")]
210# {
211use enum_ordinalize::Ordinalize;
212
213#[derive(Debug, PartialEq, Eq, Ordinalize)]
214#[ordinalize(impl_trait = false)]
215#[ordinalize(variant_count(pub const VARIANT_COUNT, doc = "The count of variants."))]
216#[ordinalize(variants(pub const VARIANTS, doc = "List of this enum's variants."))]
217#[ordinalize(values(pub const VALUES, doc = "List of values for all variants of this enum."))]
218#[ordinalize(ordinal(pub const fn ordinal, doc = "Retrieve the integer number of this variant."))]
219#[ordinalize(from_ordinal(pub const fn from_ordinal, doc = "Obtain a variant based on an integer number."))]
220#[ordinalize(from_ordinal_unsafe(
221    pub const fn from_ordinal_unsafe,
222    doc = "Obtain a variant based on an integer number.",
223    doc = "# Safety",
224    doc = "You have to ensure that the input integer number can correspond to a variant on your own.",
225))]
226enum MyEnum {
227    A,
228    B,
229}
230
231assert_eq!(2, MyEnum::VARIANT_COUNT);
232assert_eq!([MyEnum::A, MyEnum::B], MyEnum::VARIANTS);
233assert_eq!([0i8, 1i8], MyEnum::VALUES);
234
235assert_eq!(0i8, MyEnum::A.ordinal());
236assert_eq!(1i8, MyEnum::B.ordinal());
237
238assert_eq!(Some(MyEnum::A), MyEnum::from_ordinal(0i8));
239assert_eq!(Some(MyEnum::B), MyEnum::from_ordinal(1i8));
240
241assert_eq!(MyEnum::A, unsafe { MyEnum::from_ordinal_unsafe(0i8) });
242assert_eq!(MyEnum::B, unsafe { MyEnum::from_ordinal_unsafe(1i8) });
243# }
244```
245*/
246
247#![no_std]
248#![cfg_attr(docsrs, feature(doc_cfg))]
249
250#[cfg(feature = "traits")]
251mod traits;
252
253#[cfg(feature = "derive")]
254pub use enum_ordinalize_derive::Ordinalize;
255#[cfg(feature = "traits")]
256pub use traits::Ordinalize;