【发布时间】:2016-08-24 00:55:10
【问题描述】:
我可以这样做:
enum MyEnum {
A(i32),
B(i32),
}
但不是这个:
enum MyEnum {
A(123), // 123 is a constant
B(456), // 456 is a constant
}
我可以使用单个字段为A 和B 创建结构,然后实现该字段,但我认为可能有更简单的方法。有吗?
【问题讨论】:
标签: rust
我可以这样做:
enum MyEnum {
A(i32),
B(i32),
}
但不是这个:
enum MyEnum {
A(123), // 123 is a constant
B(456), // 456 is a constant
}
我可以使用单个字段为A 和B 创建结构,然后实现该字段,但我认为可能有更简单的方法。有吗?
【问题讨论】:
标签: rust
只是提供另一个想法。
#[allow(non_snake_case, non_upper_case_globals)]
mod MyEnum {
pub const A: i32 = 123;
pub const B: i32 = 456;
}
然后您可以通过访问MyEnum::A 和MyEnum::B 或use MyEnum::* 来简单地使用它。
与associated constants 相比,这样做的好处是您甚至可以嵌套更多枚举。
#[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;
}
}
对于我的项目,我编写了一个自动生成索引并设置初始值的宏。
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,)+) => {
cnum!($name; 0usize; $($n),+);
};
($name: ident; $($n:ident),*) => {
cnum!($name; 0usize; $($n),*);
};
($name: ident; $start: expr; $($n:ident,)+) => {
cnum!($name; $start; $($n),+);
};
($name: ident; $start: expr; $($n:ident),*) => {
#[allow(non_snake_case, non_upper_case_globals)]
pub mod $name {
cnum!(@step $start, $($n,)*);
}
};
// nested
($name: ident; $($n:ident,)+ { $($i:item)* }) => {
cnum!($name; 0usize; $($n),+ { $($i)* });
};
($name: ident; $($n:ident),* { $($i:item)* }) => {
cnum!($name; 0usize; $($n),* { $($i)* });
};
($name: ident; $start: expr; $($n:ident,)+ { $($i:item)* }) => {
cnum!($name; $start; $($n),+ { $($i)* });
};
($name: ident; $start: expr; $($n:ident),* { $($i:item)* }) => {
#[allow(non_snake_case, non_upper_case_globals)]
pub mod $name {
$($i)*
cnum!(@step $start, $($n,)*);
}
};
}
那你就可以这样使用了,
cnum! { Tokens;
EOF,
WhiteSpace,
Identifier,
{
cnum! { Literal; 100;
Numeric,
String,
True,
False,
Nil,
}
cnum! { Keyword; 200;
For,
If,
Return,
}
}
}
【讨论】:
这个怎么样?
enum MyEnum {
A = 123,
B = 456,
}
assert_eq!(MyEnum::A as i32, 123i32);
assert_eq!(MyEnum::B as i32, 456i32);
【讨论】:
创建具有常量值的“枚举”,可以使用结构和associated constants 进行扩充。 这类似于像 bitflags 这样的 crate 的工作方式以及它会生成什么。
此外,为了防止直接实例化MyEnum,您可以使用#[non_exhaustive] 对其进行标记。
#[non_exhaustive]
struct MyEnum;
impl MyEnum {
pub const A: i32 = 123;
pub const B: i32 = 456;
}
然后您只需访问MyEnum::A 和MyEnum::B,就可以像其他方式一样使用“枚举”。
【讨论】:
enum-map crate 提供了为枚举记录分配值的能力。更重要的是,您可以将这个宏用于不同的值类型。
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);
}
}
【讨论】:
回答这个问题的最佳方法是弄清楚为什么要在枚举中使用常量:您是要将值与每个变体相关联,还是希望每个变体成为该值(如 @ 987654321@ 在 C 或 C++ 中)?
对于第一种情况,只留下没有数据的枚举变体并创建一个函数可能更有意义:
enum MyEnum {
A,
B,
}
impl MyEnum {
fn value(&self) -> i32 {
match *self {
MyEnum::A => 123,
MyEnum::B => 456,
}
}
}
// call like some_myenum_value.value()
这种方法可以多次应用,将许多单独的信息与每个变体相关联,例如也许你也想要一个.name() -> &'static str 方法。未来,这些函数甚至可以标记为const函数。
对于第二种情况,您可以指定显式整数标记值,就像 C/C++ 一样:
enum MyEnum {
A = 123,
B = 456,
}
这可以以所有相同的方式被matched on,但也可以转换为整数MyEnum::A as i32。 (请注意,像 MyEnum::A | MyEnum::B 这样的计算在 Rust 中并不自动合法:枚举具有特定的值,它们不是位标志。)
【讨论】:
value函数中实现)?
some_myenum_value 的变量中,只需执行match some_myenum_value.value() { ...。
查看此内容的人可能会偶然发现FromPrimitive 的引入和弃用。 enum_primitive 是一个可能在这里也有用的替代品。它允许您使用类似 C 的枚举并让它们在数字和逻辑表示之间进行转换:
#[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);
}
【讨论】: