blob: 9c625f3bf5fea66e3623432096ad3e537d7360d8 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Rust enums are _closed_, meaning that the integer value distinguishing an enum, its _discriminant_,
//! must be one of the variants listed. If the integer value isn't one of those discriminants, it
//! is considered immediate [undefined behavior][ub]. This is true for enums with and without fields.
//!
//! This has some disadvantages:
//! - in constrained environments, closed enums can require premature runtime checks when using
//! `TryFrom` to convert from an integer. This is doubly true if the value will be checked again
//! at a later point, such as with a C library.
//! - an outdated binary using an enum won't preserve the value of an unknown field when reserializing
//! data without an extra `Unrecognized` value making the type more expensive than an integer.
//! - it can introduce Undefined Behavior at unexpected times if the author is unfamiliar with
//! the [rules of writing `unsafe` Rust][nomicon].
//!
//! In constrast, C++ [scoped enumerations][cpp-scoped-enum] are _open_, meaning that the enum is a
//! strongly-typed integer that could hold any value, though with a scoped set of well-known values.
//!
//! The _open enum_ pattern lets you have this in Rust. With a [newtype][newtype] and associated constants,
//! the [open_enum][open_enum] macro turns this enum declaration:
//!
//! ```
//! # use open_enum::open_enum;
//! #[open_enum]
//! enum Color {
//! Red,
//! Green,
//! Blue,
//! Orange,
//! Black,
//! }
//! ```
//!
//! into a tuple struct with associated constants:
//!
//! ```
//! #[derive(PartialEq, Eq)] // In order to work in `match`.
//! struct Color(pub i8); // Automatic integer type, can be specified with #[repr]
//!
//! impl Color {
//! pub const Red: Self = Color(0);
//! pub const Green: Self = Color(1);
//! pub const Blue: Self = Color(2);
//! pub const Orange: Self = Color(3);
//! pub const Black: Self = Color(4);
//! }
//! ```
//!
//! There are clear readability benefits to using field-less `enum`s to represent enumerated integer data.
//! It provides more type safety than a raw integer, the `enum` syntax is consise, and it provides a
//! set of constants grouped under a type that can have methods.
//!
//! # Usage
//! Usage is similar to regular `enum`s, but with some key differences.
//!
//! ```
//! # use open_enum::open_enum;
//! # #[open_enum]
//! # #[derive(Debug)]
//! # enum Color {
//! # Red,
//! # Green,
//! # Blue,
//! # Orange,
//! # Black,
//! # }
//! // Construct an open enum with the same `EnumName::VariantName` syntax.
//! let mut blood_of_angry_men = Color::Red;
//!
//! // Access the integer value with `.0`.
//! // This does not work: `Color::Red as u8`.
//! assert_eq!(blood_of_angry_men.0, 0);
//!
//! // Construct an open enum with an arbitrary integer value like any tuple struct.
//! let dark_of_ages_past = Color(4);
//!
//! // open enums always implement `PartialEq` and `Eq`, unlike regular enums.
//! assert_eq!(dark_of_ages_past, Color::Black);
//!
//! // This is outside of the known colors - but that's OK!
//! let this_is_fine = Color(10);
//!
//! // A match is always non-exhaustive - requiring a wildcard branch.
//! match this_is_fine {
//! Color::Red => panic!("a world about to dawn"),
//! Color::Green => panic!("grass"),
//! Color::Blue => panic!("蒼: not to be confused with 緑"),
//! Color::Orange => panic!("fun fact: the fruit name came first"),
//! Color::Black => panic!("the night that ends at last"),
//! // Wildcard branch, if we don't recognize the value. `x =>` also works.
//! Color(value) => assert_eq!(value, 10),
//! }
//!
//! // Unlike a regular enum, you can pass the discriminant as a reference.
//! fn increment(x: &mut i8) {
//! *x += 1;
//! }
//!
//! increment(&mut blood_of_angry_men.0);
//! // These aren't men, they're skinks!
//! assert_eq!(blood_of_angry_men, Color::Green);
//!
//! ```
//!
//! ## Integer type
//! `open_enum` will automatically determine an appropriately sized integer[^its-all-isize] to
//! represent the enum, if possible[^nonliterals-are-hard]. To choose a specific representation, it's the same
//! as a regular `enum`: add `#[repr(type)]`.
//! You can also specify `#[repr(C)]` to choose a C `int`.[^repr-c-feature][^repr-c-weird]
//!
//! If you specify an explicit `repr`, the output struct will be `#[repr(transparent)]`.
//!
//! ```
//! # use open_enum::open_enum;
//! #[open_enum]
//! #[repr(i16)]
//! #[derive(Debug)]
//! enum Fruit {
//! Apple,
//! Banana,
//! Kumquat,
//! Orange,
//! }
//!
//! assert_eq!(Fruit::Banana.0, 1i16);
//! assert_eq!(Fruit::Kumquat, Fruit(2));
//!
//! ```
//! <div class="example-wrap" style="display:inline-block"><pre class="compile_fail" style="white-space:normal;font:inherit;">
//!
//! **Warning**: `open_enum` may change the automatic integer representation for a given enum
//! in a future version with a minor version bump - it is not considered a breaking change.
//! Do not depend on this type remaining stable - use an explicit `#[repr]` for stability.
//!
//! </pre></div>
//!
//! [^its-all-isize]: Like regular `enum`s, the declared discriminant for enums without an explicit `repr`
//! is interpreted as an `isize` regardless of the automatic storage type chosen.
//!
//! [^nonliterals-are-hard]: This optimization fails if the `enum` declares a non-literal constant expression
//! as one of its discriminant values, and falls back to `isize`. To avoid this, specify an explicit `repr`.
//!
//! [^repr-c-weird]: Note that this might not actually be the correct default `enum` size for C on all platforms,
//! since the [compiler could choose something smaller than `int`](https://stackoverflow.com/a/366026).
//!
//! [^repr-c-feature]: This requires either the `std` or `libc_` feature (note the underscore)
//!
//! ## Aliasing variants
//! Regular `enum`s cannot have multiple variants with the same discriminant.
//! However, since `open_enum` produces associated constants, multiple
//! names can represent the same integer value. By default, `open_enum`
//! rejects aliasing variants, but it can be allowed with the `allow_alias` option:
//!
//! ```
//! # use open_enum::open_enum;
//! #[open_enum(allow_alias)]
//! #[derive(Debug)]
//! enum Character {
//! Viola = 0,
//! Cesario = 0,
//! Sebastian,
//! Orsino,
//! Olivia,
//! Malvolio,
//! }
//!
//! assert_eq!(Character::Viola, Character::Cesario);
//!
//! ```
//!
//!
//!
//! # Custom debug implementation
//! `open_enum` will generate a debug implementation that mirrors the standard `#[derive(Debug)]` for normal Rust enums
//! by printing the name of the variant rather than the value contained, if the value is a named variant.
//!
//! However, if an enum has `#[open_enum(allow_alias)]` specified, the debug representation will be the numeric value only.
//!
//! For example, this given enum,
//! ```
//! # use open_enum::open_enum;
//! #[open_enum]
//! #[derive(Debug)]
//! enum Fruit {
//! Apple,
//! Pear,
//! Banana,
//! Blueberry = 5,
//! Raspberry,
//! }
//! ```
//!
//! will have the following debug implementation emitted:
//! ```
//! # use open_enum::open_enum;
//! # #[open_enum]
//! # enum Fruit {
//! # Apple,
//! # Pear,
//! # Banana,
//! # Blueberry = 5,
//! # Raspberry,
//! # }
//! # impl ::core::fmt::Debug for Fruit {
//! fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
//! #![allow(unreachable_patterns)]
//! let s = match *self {
//! Self::Apple => stringify!(Apple),
//! Self::Pear => stringify!(Pear),
//! Self::Banana => stringify!(Banana),
//! Self::Blueberry => stringify!(Blueberry),
//! Self::Raspberry => stringify!(Raspberry),
//! _ => {
//! return fmt.debug_tuple(stringify!(Fruit)).field(&self.0).finish();
//! }
//! };
//! fmt.pad(s)
//! }
//! # }
//! ```
//!
//! # Compared with `#[non_exhuastive]`
//! The [`non_exhaustive`][non-exhaustive] attribute indicates that a type or variant
//! may have more fields or variants added in the future. When applied to an `enum` (not its variants),
//! it requires that foreign crates provide a wildcard arm when `match`ing.
//! Since open enums are inherently non-exhaustive[^mostly-non-exhaustive], this attribute is incompatible
//! with `open_enum`. Unlike `non_exhaustive`, open enums also require a wildcard branch on `match`es in
//! the defining crate.
//!
//! [^mostly-non-exhaustive]: Unless the enum defines a variant for every value of its underlying integer.
//!
//! # Disadvantages of open enums
//! - The kind listed in the source code, an `enum`, is not the same as the actual output, a `struct`,
//! which could be confusing or hard to debug, since its usage is similar, but not exactly the same.
//! - No niche optimization: `Option<Color>` is 1 byte as a regular enum,
//! but 2 bytes as an open enum.
//! - No pattern-matching assistance in rust-analyzer.
//! - You must have a wildcard case when pattern matching.
//! - `match`es that exist elsewhere won't break when you add a new variant,
//! similar to `#[non_exhaustive]`. However, it also means you may accidentally
//! forget to fill out a branch arm.
//!
//!
//! [cpp-scoped-enum]: https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations
//! [nomicon]: https://doc.rust-lang.org/nomicon/
//! [non-exhaustive]: https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute
//! [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
#![no_std]
/// Constructs an *open* enum from a Rust enum definition,
/// allowing it to represent more than just its listed variants.
///
/// See the [crate documentation](crate) for more details.
///
/// # Example
/// ```
/// # use open_enum::open_enum;
/// #[open_enum]
/// #[derive(Debug)]
/// enum Color {
/// Red,
/// Green,
/// Blue,
/// Orange,
/// Black,
/// }
///
/// assert_eq!(Color::Red, Color(0));
/// assert_eq!(Color(10).0, 10);
/// ```
///
/// # Options
/// - `allow_alias[ = $bool]`: default `false`. Allows duplicate discriminant values for variants.
/// - `inner_vis = $vis`: default `pub`. Specifies the visibility of the inner integer.
///
/// # Integer type
/// `open_enum` configures the discriminant type by intercepting a `repr` attribute on the enum.
/// If done, the open enum is `#[repr(transparent)]` over the provided integer type.
/// Otherwise, variant discriminants are interpreted as `isize` and an automatic integer type chosen.
///
/// # `PartialEq`/`Eq`
/// Open enums implement `PartialEq` and `Eq` in order to work in a `match` statement.
pub use open_enum_derive::open_enum;
/// Utility items only to be used by macros. Do not expect API stability.
#[doc(hidden)]
pub mod __private {
#[cfg(all(feature = "libc", not(feature = "std")))]
pub use libc::c_int;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
pub use std::os::raw::c_int;
}