在Rust中,有没有办法迭代枚举的值?

时间:2014-01-27 01:19:00

标签: enums iteration rust

我来自Java背景,我可能有类似enum Direction { NORTH, SOUTH, EAST, WEST}的东西,我可以使用增强的for循环依次对每个值做一些事情,如:

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

我想用Rust枚举做类似的事情。

7 个答案:

答案 0 :(得分:24)

如果枚举类似于C(如您的示例所示),则可以执行此操作:

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.into_iter()
    }
}


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

答案 1 :(得分:23)

不,没有。我认为这是因为Rust中的枚举比Java强大得多 - 它们实际上是完全成熟的algebraic data types。例如,您希望如何迭代此枚举的值:

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

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

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

因此,没有理智的方法可以迭代任何枚举的值。

当然,我认为,可以在编译器中添加对 static 枚举(即只包含静态项的枚举)的特殊支持,因此它会生成一些返回值的函数enum或带有它们的静态向量,但我相信编译器中的额外复杂性是不值得的。

如果您真的需要此功能,可以编写自定义语法扩展(请参阅this问题)。此扩展应接收标识符列表,并输出枚举和静态常量向量,并将这些标识符作为内容。常规宏也可以在某种程度上起作用,但据我记得你不能用多次性转录宏参数两次,所以你必须手动两次编写枚举元素,这是不方便的。

此问题也可能引起一些兴趣:#5417

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

答案 2 :(得分:8)

我在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)
})

答案 3 :(得分:5)

您现在可以使用Strum条板箱轻松地遍历枚举的值。

extern crate strum;
#[macro_use] extern crate strum_macros;

use strum::IntoEnumIterator;

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

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

输出:

NORTH
SOUTH
EAST
WEST

答案 4 :(得分:2)

您可以使用关联的常量:

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

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

答案 5 :(得分:0)

如果您不想导入第三方板条箱,则可以创建自己的宏。这是我实现的方式(可能有一些方法可以改善这一点):

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() {
        // ...
    }
}

此实现仅限于简单的枚举。考虑下面的枚举,您将如何对其进行迭代?

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

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

答案 6 :(得分:0)

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

  • 不在堆上分配
  • 是常量
  • 接受(几乎)一个实际的枚举作为输入(需要 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);
    }
}