Reputation: 34884
I can do this:
enum MyEnum {
A(i32),
B(i32),
}
but not this:
enum MyEnum {
A(123), // 123 is a constant
B(456), // 456 is a constant
}
I can create the structures for A
and B
with a single field and then implement that field, but I think there might be an easier way. Is there any?
Upvotes: 137
Views: 97340
Reputation: 26157
Creating an "enum" with constant values, can be augmented using structs and associated constants. This is similar to how crates like bitflags works and what it would generate.
Additionally, to prevent direct instantiation of MyEnum
you can tag it with #[non_exhaustive]
.
#[non_exhaustive]
struct MyEnum;
impl MyEnum {
pub const A: i32 = 123;
pub const B: i32 = 456;
}
Then you simply use the "enum" as you otherwise would, by accessing MyEnum::A
and MyEnum::B
.
Additionally, if we want to be able to iterate all variants, then we need to manually add a constant containing all variants. We could add something like this:
impl MyEnum {
pub const VARIANTS: &'static [i32] = &[Self::A, Self::B];
}
for variant in MyEnum::VARIANTS {
println!("{:?}", variant);
}
Which will output 123
then 456
.
Using macro_rules!
we can remove a lot of the boilerplate, and turn it into a simple macro:
macro_rules! def_enum {
($vis:vis $name:ident => $ty:ty {
$($variant:ident => $val:expr),+
$(,)?
}) => {
#[non_exhaustive]
$vis struct $name;
impl $name {
$(
pub const $variant: $ty = $val;
)+
pub const VARIANTS: &'static [$ty] = &[$(Self::$variant),+];
}
};
}
Now we can just do the following to define the enum
:
def_enum!(pub MyEnum => i32 {
A => 123,
B => 456,
});
Everything still remains the same. We're still able to access MyEnum::A
, MyEnum::B
, and MyEnum::VARIANTS
.
Additionally, since the macro uses a expr
and not a literal
for $val
. That also means that values can be constant expressions. In other words, the following is also completely valid:
const fn add(a: i32, b: i32) -> i32 {
a + b
}
def_enum!(pub MyEnum => i32 {
A => add(2, 3),
B => {
let (a, b) = (1, 2);
let c = (a + b) * 3;
c
},
});
assert_eq!(MyEnum::A, 5);
assert_eq!(MyEnum::B, 9);
assert_eq!(MyEnum::VARIANTS, &[5, 9]);
If we don't want i32
to be the exposed type of our variants, but instead want it to be MyEnum
, then we can wrap all i32
s in MyEnum
instead. We can achieve that by changing our macro into the following:
macro_rules! def_enum {
(
$(#[$attr:meta])*
$vis:vis $name:ident => $ty:ty {
$($variant:ident => $val:expr),+
$(,)?
}
) => {
$(#[$attr])*
$vis struct $name($ty);
impl $name {
$(
pub const $variant: Self = Self($val);
)+
pub const VARIANTS: &'static [Self] = &[$(Self::$variant),+];
pub const fn get(self) -> $ty {
self.0
}
}
};
}
If we don't care about someone doing MyEnum(12345)
, then we could just make the tuple field pub
. Otherwise, we'd need a get()
method (which the above includes.)
Additionally, the addition of $attr:meta
allows us to add #[derive(...)]
and any other attributes.
def_enum!(
#[derive(PartialEq,Eq, Debug)]
pub MyEnum => i32 {
A => 123,
B => 456,
}
);
assert_eq!(MyEnum::A.get(), 5);
assert_eq!(MyEnum::B.get(), 9);
assert_eq!(MyEnum::VARIANTS, &[MyEnum::A, MyEnum::B]);
Upvotes: 52
Reputation: 39
I have created a crate enumeration just for this.
Example using my crate:
use enumeration::prelude::*;
enumerate!(MyEnum(u8; i32)
A = 123
B = 456
);
pub fn main() {
assert_eq!(*MyEnum::A.value(), 123);
assert_eq!(*MyEnum::B.value(), 456);
}
Upvotes: 0
Reputation: 102006
The best way to answer this is working out why you want constants in an enum: are you associating a value with each variant, or do you want each variant to be that value (like an enum
in C or C++)?
For the first case, it probably makes more sense to just leave the enum variants with no data, and make a function:
enum MyEnum {
A,
B,
}
impl MyEnum {
fn value(&self) -> i32 {
match *self {
MyEnum::A => 123,
MyEnum::B => 456,
}
}
}
// call like some_myenum_value.value()
This approach can be applied many times, to associate many separate pieces of information with each variant, e.g. maybe you want a .name() -> &'static str
method too. In the future, these functions can even be marked as const
functions.
For the second case, you can assign explicit integer tag values, just like C/C++:
enum MyEnum {
A = 123,
B = 456,
}
This can be matched
on in all the same ways, but can also be cast to an integer MyEnum::A as i32
. (Note that computations like MyEnum::A | MyEnum::B
are not automatically legal in Rust: enums have specific values, they're not bit-flags.)
Upvotes: 178
Reputation: 171
Just to give another idea.
#[allow(non_snake_case, non_upper_case_globals)]
mod MyEnum {
pub const A: i32 = 123;
pub const B: i32 = 456;
}
Then you can simply use it by accessing MyEnum::A
and MyEnum::B
or use MyEnum::*
.
The advantage of doing this over associated constants is that you can even nest more enums.
#[allow(non_snake_case, non_upper_case_globals)]
mod MyEnum {
pub const A: i32 = 123;
pub const B: i32 = 456;
#[allow(non_snake_case, non_upper_case_globals)]
mod SubEnum {
pub const C: i32 = 789;
}
}
For my project I wrote a macro that automatically generates indexes and sets initial values.
#[macro_export]
macro_rules! cnum {
(@step $_idx:expr,) => {};
(@step $idx:expr, $head:ident, $($tail:ident,)*) => {
pub const $head: usize = $idx;
cnum!(@step $idx + 1usize, $($tail,)*);
};
($name:ident; $($n:ident),* $(,)* $({ $($i:item)* })?) => {
cnum!($name; 0usize; $($n),* $({ $($i)* })?);
};
($name:ident; $start:expr; $($n:ident),* $(,)* $({ $($i:item)* })?) => {
#[macro_use]
#[allow(dead_code, non_snake_case, non_upper_case_globals)]
pub mod $name {
use crate::cnum;
$($($i)*)?
cnum!(@step $start, $($n,)*);
}
};
}
Then you can use it like this,
cnum! { Tokens;
EOF,
WhiteSpace,
Identifier,
{
cnum! { Literal; 100;
Numeric,
String,
True,
False,
Nil,
}
cnum! { Keyword; 200;
For,
If,
Return,
}
}
}
Upvotes: 3
Reputation: 81
How about this?
enum MyEnum {
A = 123,
B = 456,
}
assert_eq!(MyEnum::A as i32, 123i32);
assert_eq!(MyEnum::B as i32, 456i32);
Upvotes: 5
Reputation: 264
The enum-map crate provides the ability to assign a value to the enum record. What is more, you can use this macro with different value types.
use enum_map::{enum_map, Enum}; // 0.6.2
#[derive(Debug, Enum)]
enum Example {
A,
B,
C,
}
fn main() {
let mut map = enum_map! {
Example::A => 1,
Example::B => 2,
Example::C => 3,
};
map[Example::C] = 4;
assert_eq!(map[Example::A], 1);
for (key, &value) in &map {
println!("{:?} has {} as value.", key, value);
}
}
Upvotes: 3
Reputation: 16475
People looking at this may stumble upon the introduction and deprecation of FromPrimitive
. A possible replacement which might also be useful here is enum_primitive. It allows you to use C-like enums and have them cast between numeric and logical representation:
#[macro_use]
extern crate enum_primitive;
extern crate num;
use num::FromPrimitive;
enum_from_primitive! {
#[derive(Debug, PartialEq)]
enum FooBar {
Foo = 17,
Bar = 42,
Baz,
}
}
fn main() {
assert_eq!(FooBar::from_i32(17), Some(FooBar::Foo));
assert_eq!(FooBar::from_i32(42), Some(FooBar::Bar));
assert_eq!(FooBar::from_i32(43), Some(FooBar::Baz));
assert_eq!(FooBar::from_i32(91), None);
}
Upvotes: 7