Skip to main content

open_enum/
lib.rs

1//! Rust enums are _closed_, meaning that the integer value distinguishing an enum, its _discriminant_,
2//! must be one of the variants listed. If the integer value isn't one of those discriminants, it
3//! is considered immediate [undefined behavior][ub]. This is true for enums with and without fields.
4//!
5//! This has some disadvantages:
6//! - in constrained environments, closed enums can require premature runtime checks when using
7//!   `TryFrom` to convert from an integer. This is doubly true if the value will be checked again
8//!   at a later point, such as with a C library.
9//! - an outdated binary using an enum won't preserve the value of an unknown field when reserializing
10//!   data without an extra `Unrecognized` value making the type more expensive than an integer.
11//! - it can introduce Undefined Behavior at unexpected times if the author is unfamiliar with
12//!   the [rules of writing `unsafe` Rust][nomicon].
13//!
14//! In constrast, C++ [scoped enumerations][cpp-scoped-enum] are _open_, meaning that the enum is a
15//! strongly-typed integer that could hold any value, though with a scoped set of well-known values.
16//!
17//! The _open enum_ pattern lets you have this in Rust. With a [newtype][newtype] and associated constants,
18//! the [open_enum][open_enum] macro turns this enum declaration:
19//!
20//! ```
21//! # use open_enum::open_enum;
22//! #[open_enum]
23//! enum Color {
24//!     Red,
25//!     Green,
26//!     Blue,
27//!     Orange,
28//!     Black,
29//! }
30//! ```
31//!
32//! into a tuple struct with associated constants:
33//!
34//! ```
35//! #[derive(PartialEq, Eq)]  // In order to work in `match`.
36//! struct Color(pub i8);  // Automatic integer type, can be specified with #[repr]
37//!
38//! impl Color {
39//!     pub const Red: Self = Color(0);
40//!     pub const Green: Self = Color(1);
41//!     pub const Blue: Self = Color(2);
42//!     pub const Orange: Self = Color(3);
43//!     pub const Black: Self = Color(4);
44//! }
45//! ```
46//!
47//! There are clear readability benefits to using field-less `enum`s to represent enumerated integer data.
48//! It provides more type safety than a raw integer, the `enum` syntax is consise, and it provides a
49//! set of constants grouped under a type that can have methods.
50//!
51//! # Usage
52//! Usage is similar to regular `enum`s, but with some key differences.
53//!
54//! ```
55//! # use open_enum::open_enum;
56//! # #[open_enum]
57//! # #[derive(Debug)]
58//! # enum Color {
59//! #     Red,
60//! #     Green,
61//! #     Blue,
62//! #     Orange,
63//! #     Black,
64//! # }
65//! // Construct an open enum with the same `EnumName::VariantName` syntax.
66//! let mut blood_of_angry_men = Color::Red;
67//!
68//! // Access the integer value with `.0`.
69//! // This does not work: `Color::Red as u8`.
70//! assert_eq!(blood_of_angry_men.0, 0);
71//!
72//! // Construct an open enum with an arbitrary integer value like any tuple struct.
73//! let dark_of_ages_past = Color(4);
74//!
75//! // open enums always implement `PartialEq` and `Eq`, unlike regular enums.
76//! assert_eq!(dark_of_ages_past, Color::Black);
77//!
78//! // This is outside of the known colors - but that's OK!
79//! let this_is_fine = Color(10);
80//!
81//! // A match is always non-exhaustive - requiring a wildcard branch.
82//! match this_is_fine {
83//!     Color::Red => panic!("a world about to dawn"),
84//!     Color::Green => panic!("grass"),
85//!     Color::Blue => panic!("蒼: not to be confused with 緑"),
86//!     Color::Orange => panic!("fun fact: the fruit name came first"),
87//!     Color::Black => panic!("the night that ends at last"),
88//!     // Wildcard branch, if we don't recognize the value. `x =>` also works.
89//!     Color(value) => assert_eq!(value, 10),
90//! }
91//!
92//! // Unlike a regular enum, you can pass the discriminant as a reference.
93//! fn increment(x: &mut i8) {
94//!     *x += 1;
95//! }
96//!
97//! increment(&mut blood_of_angry_men.0);
98//! // These aren't men, they're skinks!
99//! assert_eq!(blood_of_angry_men, Color::Green);
100//!
101//! ```
102//!
103//! ## Integer type
104//! `open_enum` will automatically determine an appropriately sized integer[^its-all-isize] to
105//! represent the enum, if possible[^nonliterals-are-hard]. To choose a specific representation, it's the same
106//! as a regular `enum`: add `#[repr(type)]`.
107//! You can also specify `#[repr(C)]` to choose a C `int`.[^repr-c-feature][^repr-c-weird]
108//!
109//! If you specify an explicit `repr`, the output struct will be `#[repr(transparent)]`.
110//!
111//! ```
112//! # use open_enum::open_enum;
113//! #[open_enum]
114//! #[repr(i16)]
115//! #[derive(Debug)]
116//! enum Fruit {
117//!     Apple,
118//!     Banana,
119//!     Kumquat,
120//!     Orange,
121//! }
122//!
123//! assert_eq!(Fruit::Banana.0, 1i16);
124//! assert_eq!(Fruit::Kumquat, Fruit(2));
125//!
126//! ```
127//!  <div class="example-wrap" style="display:inline-block"><pre class="compile_fail" style="white-space:normal;font:inherit;">
128//!
129//!  **Warning**: `open_enum` may change the automatic integer representation for a given enum
130//! in a future version with a minor version bump - it is not considered a breaking change.
131//! Do not depend on this type remaining stable - use an explicit `#[repr]` for stability.
132//!
133//! </pre></div>
134//!
135//! [^its-all-isize]: Like regular `enum`s, the declared discriminant for enums without an explicit `repr`
136//! is interpreted as an `isize` regardless of the automatic storage type chosen.
137//!
138//! [^nonliterals-are-hard]: This optimization fails if the `enum` declares a non-literal constant expression
139//! as one of its discriminant values, and falls back to `isize`. To avoid this, specify an explicit `repr`.
140//!
141//! [^repr-c-weird]: Note that this might not actually be the correct default `enum` size for C on all platforms,
142//!                  since the [compiler could choose something smaller than `int`](https://stackoverflow.com/a/366026).
143//!
144//! [^repr-c-feature]: This requires either the `std` or `libc_` feature (note the underscore)
145//!
146//! ## Aliasing variants
147//! Regular `enum`s cannot have multiple variants with the same discriminant.
148//! However, since `open_enum` produces associated constants, multiple
149//! names can represent the same integer value. By default, `open_enum`
150//! rejects aliasing variants, but it can be allowed with the `allow_alias` option:
151//!
152//! ```
153//! # use open_enum::open_enum;
154//! #[open_enum(allow_alias)]
155//! #[derive(Debug)]
156//! enum Character {
157//!     Viola = 0,
158//!     Cesario = 0,
159//!     Sebastian,
160//!     Orsino,
161//!     Olivia,
162//!     Malvolio,
163//! }
164//!
165//! assert_eq!(Character::Viola, Character::Cesario);
166//!
167//! ```
168//!
169//!
170//!
171//! # Custom debug implementation
172//! `open_enum` will generate a debug implementation that mirrors the standard `#[derive(Debug)]` for normal Rust enums
173//! by printing the name of the variant rather than the value contained, if the value is a named variant.
174//!
175//! However, if an enum has `#[open_enum(allow_alias)]` specified, the debug representation will be the numeric value only.
176//!
177//! For example, this given enum,
178//! ```
179//! # use open_enum::open_enum;
180//! #[open_enum]
181//! #[derive(Debug)]
182//! enum Fruit {
183//!     Apple,
184//!     Pear,
185//!     Banana,
186//!     Blueberry = 5,
187//!     Raspberry,
188//! }
189//! ```
190//!
191//! will have the following debug implementation emitted:
192//! ```
193//! # use open_enum::open_enum;
194//! # #[open_enum]
195//! # enum Fruit {
196//! #     Apple,
197//! #     Pear,
198//! #     Banana,
199//! #     Blueberry = 5,
200//! #     Raspberry,
201//! # }
202//! # impl ::core::fmt::Debug for Fruit {
203//! fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
204//!         #![allow(unreachable_patterns)]
205//!         let s = match *self {
206//!             Self::Apple => stringify!(Apple),
207//!             Self::Pear => stringify!(Pear),
208//!             Self::Banana => stringify!(Banana),
209//!             Self::Blueberry => stringify!(Blueberry),
210//!             Self::Raspberry => stringify!(Raspberry),
211//!             _ => {
212//!                 return fmt.debug_tuple(stringify!(Fruit)).field(&self.0).finish();
213//!             }
214//!         };
215//!         fmt.pad(s)
216//!     }
217//! # }
218//! ```
219//!
220//! # Compared with `#[non_exhuastive]`
221//! The [`non_exhaustive`][non-exhaustive] attribute indicates that a type or variant
222//! may have more fields or variants added in the future. When applied to an `enum` (not its variants),
223//! it requires that foreign crates provide a wildcard arm when `match`ing.
224//! Since open enums are inherently non-exhaustive[^mostly-non-exhaustive], this attribute is incompatible
225//! with `open_enum`. Unlike `non_exhaustive`, open enums also require a wildcard branch on `match`es in
226//! the defining crate.
227//!
228//! [^mostly-non-exhaustive]: Unless the enum defines a variant for every value of its underlying integer.
229//!
230//! # Disadvantages of open enums
231//! - The kind listed in the source code, an `enum`, is not the same as the actual output, a `struct`,
232//!   which could be confusing or hard to debug, since its usage is similar, but not exactly the same.
233//! - No niche optimization: `Option<Color>` is 1 byte as a regular enum,
234//!   but 2 bytes as an open enum.
235//! - No pattern-matching assistance in rust-analyzer.
236//! - You must have a wildcard case when pattern matching.
237//! - `match`es that exist elsewhere won't break when you add a new variant,
238//!   similar to `#[non_exhaustive]`. However, it also means you may accidentally
239//!   forget to fill out a branch arm.
240//!
241//!
242//! [cpp-scoped-enum]: https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations
243//! [nomicon]: https://doc.rust-lang.org/nomicon/
244//! [non-exhaustive]: https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute
245//! [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
246
247#![no_std]
248
249/// Constructs an *open* enum from a Rust enum definition,
250/// allowing it to represent more than just its listed variants.
251///
252/// See the [crate documentation](crate) for more details.
253///
254/// # Example
255/// ```
256/// # use open_enum::open_enum;
257/// #[open_enum]
258/// #[derive(Debug)]
259/// enum Color {
260///     Red,
261///     Green,
262///     Blue,
263///     Orange,
264///     Black,
265/// }
266///
267/// assert_eq!(Color::Red, Color(0));
268/// assert_eq!(Color(10).0, 10);
269/// ```
270///
271/// # Options
272/// - `allow_alias[ = $bool]`: default `false`. Allows duplicate discriminant values for variants.
273/// - `inner_vis = $vis`: default `pub`. Specifies the visibility of the inner integer.
274///
275/// # Integer type
276/// `open_enum` configures the discriminant type by intercepting a `repr` attribute on the enum.
277/// If done, the open enum is `#[repr(transparent)]` over the provided integer type.
278/// Otherwise, variant discriminants are interpreted as `isize` and an automatic integer type chosen.
279///
280/// # `PartialEq`/`Eq`
281/// Open enums implement `PartialEq` and `Eq` in order to work in a `match` statement.
282pub use open_enum_derive::open_enum;
283
284/// Utility items only to be used by macros. Do not expect API stability.
285#[doc(hidden)]
286pub mod __private {
287    #[cfg(all(feature = "libc", not(feature = "std")))]
288    pub use libc::c_int;
289
290    #[cfg(feature = "std")]
291    extern crate std;
292
293    #[cfg(feature = "std")]
294    pub use std::os::raw::c_int;
295}