Expand description
§Enum Extension Library
This Rust crate provides procedural and attribute macros that enhance Rust enums with additional methods and
conversions. It simplifies working with enums by automatically generating utility methods for common tasks such as
retrieving a list of variants, counting variants, and converting between discriminants and integer types.
See the enum_ext! and #[enum_extend] macro examples below for more information.
Both macros generate the same utility methods, so you can choose the one that best fits your coding style.
§Utility Functions
§Core Functions
list(): Returns an array containing all variants of the enum.count(): Returns the number of variants in the enum.ordinal(): Returns the ordinal (index) of a variant.from_ordinal(ordinal: usize): Returns the variant corresponding to the given ordinal.ref_from_ordinal(ordinal: usize): Returns a reference to the variant corresponding to the given ordinal.valid_ordinal(ordinal: usize): Checks if the given ordinal is valid for the enum.iter(): Returns an iterator over all the variants of the enum.pretty_print(): Returns a formatted string displaying the enum and all its variants in a pretty-print format.
§String Conversion Functions
pascal_spaced(&self): Converts the variant name to spaced PascalCase. For instance,InQAbecomes"In QA".from_pascal_spaced(name: &str): Returns the variant corresponding to the spaced PascalCase name. For example,"In QA"becomesInQA.snake_case(&self): Converts the variant name to snake_case. For instance,InQAbecomes"in_qa".from_snake_case(name: &str): Returns the variant corresponding to the snake_case name. For example,"in_qa"becomesInQA.kebab_case(&self): Converts the variant name to kebab-case. For instance,InQAbecomes"in-qa".from_kebab_case(name: &str): Returns the variant corresponding to the kebab-case name. For example,"in-qa"becomesInQA.
§Navigation Functions
next(&self): Returns the next variant in ordinal order (wraps around to first when at last).previous(&self): Returns the previous variant in ordinal order (wraps around to last when at first).next_linear(&self): Returns the next variant without wrapping (returnsNoneat end).previous_linear(&self): Returns the previous variant without wrapping (returnsNoneat start).
§Validation Functions
is_first(&self): Returnstrueif this is the first variant (ordinal 0).is_last(&self): Returnstrueif this is the last variant.comes_before(&self, other: &Self): Returnstrueif this variant comes before the other in ordinal order.comes_after(&self, other: &Self): Returnstrueif this variant comes after the other in ordinal order.
§Filtering Functions
variants_containing(substring: &str): Returns variants whose names contain the substring.variants_starting_with(prefix: &str): Returns variants whose names start with the prefix.variants_ending_with(suffix: &str): Returns variants whose names end with the suffix.
§Batch Operations
slice(start: usize, end: usize): Returns a slice of variants from start to end ordinal.range(range: core::ops::Range<usize>): Returns variants in the specified ordinal range.first_n(n: usize): Returns the first N variants.last_n(n: usize): Returns the last N variants.
§Metadata Functions
variant_name(&self): Returns the variant name as a string.variant_names(): Returns all variant names as a vector of strings.
§Random Selection (Optional Feature)
random(): Returns a random variant (requires"random"feature).random_with_rng<R: Rng>(rng: &mut R): Returns a random variant using provided RNG (requires"random"feature).
§Integer Conversion Functions (When IntType is specified)
from_<IntType>(value: <IntType>)andas_<IntType>(&self): Convert to and from the specified integer type, if defined in the attributes.- For example,
from_i32(10)andas_i32()ifIntType = "i32", orfrom_u32(10)andas_u32()ifIntType = "u32", etc.
- For example,
§See examples in the repository for more information.
§Attributes
Attributes are optional and used to customize the generated methods.
IntTypeis currently the only attribute supported and specifies the discriminant type for conversion methods. The generated methods allow conversion from this type to an enum variant and vice versa. Supported types include standard Rust integer types likei32,u32,i64, etc. If this attribute is not specified,usizeis used as the default.- Note: If the enum has discriminant values,
#[derive(Clone)]is added to the enum (if not already present).
- Note: If the enum has discriminant values,
§Features
This crate supports optional features that can be enabled in your Cargo.toml:
random- Enables random variant selection functionality (random()andrandom_with_rng()methods). Add this to yourCargo.toml:[dependencies] rand = "0.9" enum_ext = { version = "0.5.1", features = ["random"] }
Assigning attributes vary slightly depending on the macro used.
When using enum_extend, the attribute is applied directly in the tag:
use enum_ext::enum_extend;
// example with no attribute
#[enum_extend]
#[derive(Debug, Clone, PartialEq)]
pub enum Discr1 {
A = 10,
B = 20,
C = 30,
}
// example with an attribute
#[enum_extend(IntType = "i32")] // <- `IntType` is the discriminant type for conversion methods
#[derive(Debug, Clone, PartialEq)]
pub enum Discr2 {
A = 10,
B = 20,
C = 30,
}
When using enum_ext!, the attribute is applied in an enum_def parameter to the macro:
use enum_ext::enum_ext;
enum_ext!(
#[enum_def(IntType = "i32")] // <- `IntType` is the discriminant type.
#[derive(Debug, Clone, PartialEq)]
pub enum AdvancedEnum {
A = 10,
B = 20,
C = 30,
}
);§Usage
§Using the #[enum_extend] Attribute Macro
To use the enum_extend attribute macro, simply include it in your Rust project and apply it to your enum definitions. Here’s an example:
fn main() {
use enum_ext::enum_extend;
#[enum_extend(IntType = "i32")]
#[derive(Debug, Default, Clone, PartialEq)]
pub enum AdvancedEnum {
#[default]
A = 10,
B = 20,
C = 30,
}
for x in AdvancedEnum::iter() {
let i = x.as_i32();
let v = AdvancedEnum::from_i32(i).unwrap();
assert_eq!(i, v.as_i32());
assert_eq!(*x, v); // This comparison requires that PartialEq be derived
}
let v = AdvancedEnum::from_i32(20).unwrap();
assert_eq!(v, AdvancedEnum::B);
}§Using the enum_ext! Procedural Macro
To use the enum_ext! macro, simply include it in your Rust project and apply it to your enum definitions. Here’s an
example:
fn main() {
use enum_ext::enum_ext;
enum_ext!(
#[derive(Debug, Clone, PartialEq)]
pub enum SimpleEnum {
A,
B,
C,
}
);
// With this, you can now use the generated methods on SimpleEnum:
let x = SimpleEnum::B;
assert_eq!(x.ordinal(), 1); // B is the second variant, so its ordinal is 1
let mut count = 0;
// enum_ext gives enums an iterator and variants can be iterated over
for x in SimpleEnum::iter() {
// The ordinal of the variant can be retrieved
let i = x.ordinal();
assert_eq!(i, count);
count += 1;
}
// enums also get a list method that returns an array of all variants
let list = SimpleEnum::list();
assert_eq!(list, [SimpleEnum::A, SimpleEnum::B, SimpleEnum::C]);
enum_ext!(
#[derive(Debug, Clone, Default, PartialEq)]
pub enum TicketStatus {
#[default]
Open,
InDev,
Completed,
InQA,
CodeReview,
FinalQA,
FinalCodeReview,
Accepted,
Closed,
}
);
// enums now have a pascal_spaced method that returns the variant name in spaced PascalCase.
// This is useful for displaying enum variants in a user-friendly format (e.g., in a UI).
// One example usage is converting InQA to "In QA" for display on a web page.
let status = TicketStatus::InQA;
assert_eq!(status.pascal_spaced(), "In QA");
// enums also get a from_pascal_spaced method that returns the variant from the spaced PascalCase name.
// This is useful for converting user-friendly format back to an enum variant.
// This is the reverse of the example above, converting "In QA" back to an enum.
let status2 = TicketStatus::from_pascal_spaced("In QA").unwrap();
assert_eq!(status2, TicketStatus::InQA);
}Additional utility methods are generated for the enum variants:
use enum_ext::enum_extend;
#[enum_extend(IntType = "i32")]
#[derive(Debug, PartialEq)]
pub enum DevelopmentStatus {
InDev = 10,
InQA = 20,
CodeReview = 30,
FinalQA = 40,
FinalCodeReview = 50,
Accepted = 60,
Closed = 70,
}
fn main() {
// Using list()
let variants = DevelopmentStatus::list();
assert_eq!(variants,
[DevelopmentStatus::InDev,
DevelopmentStatus::InQA,
DevelopmentStatus::CodeReview,
DevelopmentStatus::FinalQA,
DevelopmentStatus::FinalCodeReview,
DevelopmentStatus::Accepted,
DevelopmentStatus::Closed]);
// Using count()
let count = DevelopmentStatus::count();
assert_eq!(count, 7);
// Using ordinal()
let ordinal = DevelopmentStatus::CodeReview.ordinal();
assert_eq!(ordinal, 2); // CodeReview is the third variant, so its ordinal is 2
assert_eq!(DevelopmentStatus::from_ordinal(2), Some(DevelopmentStatus::CodeReview));
// Using iter()
for (i, variant) in DevelopmentStatus::iter().enumerate() {
assert_eq!(i, variant.ordinal());
}
// Using from_i32() and as_i32()
let variant = DevelopmentStatus::from_i32(20).unwrap();
assert_eq!(variant, DevelopmentStatus::InQA);
assert_eq!(variant.as_i32(), 20);
// Using pascal_spaced() method that returns the variant name in spaced PascalCase.
// This is useful for displaying enum variants in a user-friendly format (e.g., in a UI).
// One example usage is converting InQA to "In QA" for display on a web page.
let status = DevelopmentStatus::InQA;
assert_eq!(status.pascal_spaced(), "In QA");
// Using from_pascal_spaced() method that returns the variant from the spaced PascalCase name.
// This is useful for converting user-friendly format back to an enum variant.
// This is the reverse of the example above, converting "In QA" back to an enum.
let status2 = DevelopmentStatus::from_pascal_spaced("In QA").unwrap();
assert_eq!(status2, DevelopmentStatus::InQA);
}§Complex enum support
Starting with v0.5.0, enums with payloads (tuple or struct variants) are supported. These are referred to as complex enums below.
Requirements and recommendations:
- Every complex enum variant must declare an explicit discriminant expression (for example, A(u32) = 4, B((u32, i16)) = 8). The macro will emit a compile error if any payload-carrying variant is missing a discriminant.
- The integer representation (#[repr(..)]) is added automatically when you specify IntType, or when discriminants are present. If you do not specify IntType, the default conversion target is usize and as_usize will be generated.
What is generated for complex enums:
- Provided methods (all const and using match on self):
- count(), ordinal(), valid_ordinal()
- pascal_spaced(), snake_case(), kebab_case()
- variant_name(), is_first(), is_last(), comes_before(), comes_after()
as_<IntType>(&self) -> <IntType>(for example,as_u32()), which returns the discriminant value
- Omitted methods (not generated for complex enums because they require constructing values without payloads):
- list(), iter(), slice(), range(), first_n(), last_n()
- from_ordinal(), ref_from_ordinal(), next(), previous(), next_linear(), previous_linear()
from_<IntType>(...),impl From<<IntType>> for YourEnum- from_pascal_spaced(…), from_snake_case(…), from_kebab_case(…), variant_names()
- random() helpers (feature = “random”)
Example:
use enum_ext::enum_extend;
#[enum_extend(IntType = "u32")] // IntType is optional; defaults to usize when omitted
#[derive(Debug, Clone, Copy, PartialEq)]
enum Complex {
AlphaOne(u32) = 4,
BetaTwo((u32, i16)) = 8,
CharlieThree { fred: u32, barny: i16 } = 16,
}
fn main() {
let a = Complex::AlphaOne(10);
let b = Complex::BetaTwo((1, -2));
let c = Complex::CharlieThree { fred: 5, barny: -7 };
// integer conversion retains match-based logic and remains const
assert_eq!(a.as_u32(), 4);
assert_eq!(b.as_u32(), 8);
assert_eq!(c.as_u32(), 16);
// ordinal and name helpers still work
assert_eq!(a.ordinal(), 0);
assert_eq!(a.pascal_spaced(), "Alpha One");
assert_eq!(a.snake_case(), "alpha_one");
assert_eq!(a.kebab_case(), "alpha-one");
}§Changes
§v0.5.1
- Improved internal deterministic hasher.
- bumped dependencies.
§v0.5.0
- Added support for complex enums (variants with payloads)
- note: not all utility features are possible for complex enums and are omitted from these types of enums only (non-complex enums still have them). see the Complex enum support section for more details.
use enum_ext::enum_extend; #[enum_extend(IntType = "i32")] #[derive(Debug, Clone, PartialEq)] pub enum DiscrExpression { // singles X10(u32) = 10, // tuples X25((i32,i16)) = 5 * 5, // structs Y26{foo: u32,bar: String} = 13 + 13, }
§v0.4.5
- Added support for discriminant expressions, instead of just literals.
use enum_ext::enum_extend; #[enum_extend(IntType = "i32")] #[derive(Debug, Clone, Copy, PartialEq)] pub enum DiscrExpression { // literals X10 = 10, // expressions X25 = 5 * 5, Y26 = 13 + 13, Z100 = 10 * (5 + 5), }
§v0.4.4
- swapped
std::ops::Rangewithcore::ops::Rangefor compatibility withno_std
§v0.4.3
- as_<int_type> is now
constfn if dev derivesCopyon enum.
§v0.4.2
- Parse the configured
IntTypeinto a real Rust type usingsyn::parse_str::<syn::Type>instead of string-based token hacks; this makesIntTypehandling more robust and prevents malformed type tokens from being emitted. - Emit
#[repr(...)]whenever the user explicitly specifiesIntType(even if no discriminants are present), so the enum layout matches the requested integer representation. - Reject multiple
#[enum_def(...)]attributes on the derive macro; the macro now returns a clear compile error if more than oneenum_defattribute is present. - Use the local
EnumDefArgs::default()directly and tidy up attribute parsing code paths for clarity. - Improve tests and validation across the macros;
Macros§
- enum_
ext - Enum Extension Library
Attribute Macros§
- enum_
extend - Enum Extension Library