open_enum/
lib.rs

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