在 Rust 中,是否有一种方法可以迭代枚举的值?

我来自 Java 背景,我可能有类似于 enum Direction { NORTH, SOUTH, EAST, WEST}的东西,我可以用增强的 for 循环依次处理每个值,比如:

for(Direction dir : Direction.values())  {
//do something with dir
}

我想对 Rust 枚举做一个类似的事情。

62102 次浏览

不,没有。我认为这是因为 Rust 中的枚举比 Java 中的枚举强大得多——它们实际上是成熟的 代数数据类型。例如,您希望如何迭代这个枚举的值:

enum Option<T> {
None,
Some(T)
}

?

它的第二个成员 Some不是静态常量——您可以使用它来创建 Option<T>的值:

let x = Some(1);
let y = Some("abc");

因此,没有合理的方法可以遍历 any枚举的值。

Of course, I think, it would be possible to add special support for 静电干扰 enums (i.e. enums with only static items) into the compiler, so it would generate some function which return values of the enum or a static vector with them, but I believe that additional complexity in the compiler is just not worth it.

If you really want this functionality, you could write a custom syntax extension (see 这个 issue). This extension should receive a list of identifiers and output an enum and a static constant vector with these identifiers as a content. A regular macro would also work to some extent, but as far as I remember you cannot transcript macro arguments with multiplicity twice, so you'll have to write enum elements twice manually, which is not convenient.

此外,这个问题可能是一些感兴趣的: # 5417

当然,您总是可以编写手动返回枚举元素列表的代码。

如果枚举与 C 类似(如本例所示) ,那么您可以创建每个变量的 static数组,并返回对它们的引用的迭代器:

use self::Direction::*;
use std::slice::Iter;


#[derive(Debug)]
pub enum Direction {
North,
South,
East,
West,
}


impl Direction {
pub fn iterator() -> Iter<'static, Direction> {
static DIRECTIONS: [Direction; 4] = [North, South, East, West];
DIRECTIONS.iter()
}
}


fn main() {
for dir in Direction::iterator() {
println!("{:?}", dir);
}
}

If you make the enum implement Copy, you can use Iterator::copied and return impl Trait to have an iterator of values:

impl Direction {
pub fn iterator() -> impl Iterator<Item = Direction> {
[North, South, East, West].iter().copied()
}
}

参见:

我在 crate plain_enum中实现了基本功能。

它可以用来声明一个类 C 的枚举,如下所示:

#[macro_use]
extern crate plain_enum;


plain_enum_mod!(module_for_enum, EnumName {
EnumVal1,
EnumVal2,
EnumVal3,
});

然后允许你做如下事情:

for value in EnumName::values() {
// do things with value
}


let enummap = EnumName::map_from_fn(|value| {
convert_enum_value_to_mapped_value(value)
})

您可以使用 弹奏板条箱轻松地迭代枚举的值。

use strum::IntoEnumIterator; // 0.17.1
use strum_macros::EnumIter; // 0.17.1


#[derive(Debug, EnumIter)]
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}


fn main() {
for direction in Direction::iter() {
println!("{:?}", direction);
}
}

产出:

NORTH
SOUTH
EAST
WEST

如果您不想导入第三方板条箱,您可以制作自己的宏来完成。下面是我是如何做到的(可能有一些方法可以改善这一点) :

macro_rules! iterable_enum {
($visibility:vis, $name:ident, $($member:tt),*) => {
$visibility enum $name {$($member),*}
impl $name {
fn iterate() -> Vec<$name> {
vec![$($name::$member,)*]
}
}
};
($name:ident, $($member:tt),*) => {
iterable_enum!(, $name, $($member),*)
};
}

然后你可以做:

iterable_enum!(pub, EnumName, Value1, Value2, Value3);


fn main() {
for member in EnumName::iterate() {
// ...
}
}

This implementation is limited to simple enums. Consider the following enum, how would you iterate over it?:

enum MoreComplexEnum<T1, T2> {
One(T1),
Two(T2),
Other,
Both(T1, T2),
Error(String),
}

由于 Rust 中枚举的强大功能,很难实现完全可迭代的枚举,因为它们与 C 或 Java 中的简单枚举不同。

可以使用关联常量:

impl Direction {
const VALUES: [Self; 4] = [Self::NORTH, Self::SOUTH, Self::EAST, Self::WEST];
}


fn main() {
for direction in Direction::VALUES.iter().copied() {
todo!();
}
}

以下是我对@Ahmed Merez 回答的看法:

  • doesn't allocate on the heap
  • 就是康斯特
  • 接受(几乎)一个实际的枚举作为输入(需要 vis,因为 Rust 似乎不喜欢一个错误为 error: repetition matches empty token tree的空可见性参数) 包括:
    • 推导输入
    • 属性属性
#[macro_export]
macro_rules! count {
() => (0usize);
( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*));
}


/// https://stackoverflow.com/a/64678145/10854888
macro_rules! iterable_enum {
($(#[$derives:meta])* $(vis $visibility:vis)? enum $name:ident { $($(#[$nested_meta:meta])* $member:ident),* }) => {
const count_members:usize = $crate::count!($($member)*);
$(#[$derives])*
$($visibility)? enum $name {
$($(#[$nested_meta])* $member),*
}
impl $name {
pub const fn iter() -> [$name; count_members] {
[$($name::$member,)*]
}
}
};
}




fn main() {
iterable_enum! {
#[derive(Debug, serde::Deserialize)]
vis pub(crate) enum X {
#[serde(rename="a")]
A,
B
}
}
    

for x in X::iter() {
dbg!(x);
}
}

The 枚举迭代器枚举迭代器 crate helps iterating enumerators.