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

8ftvxx2r  于 2023-02-23  发布在  其他
关注(0)|答案(9)|浏览(274)

我有Java背景,我可能有enum Direction { NORTH, SOUTH, EAST, WEST}这样的代码,我可以使用增强的for循环依次处理每个值,例如:

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

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

3bygqnnd

3bygqnnd1#

可以使用strum crate轻松地迭代枚举的值。

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
blmhpbnm

blmhpbnm2#

如果枚举类似于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);
    }
}

如果你让枚举实现Copy,你可以使用Iterator::copied并返回impl Trait来得到一个值的迭代器:

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

另见:

qvtsj1bj

qvtsj1bj3#

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

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


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

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

因此,没有合理的方法可以迭代 any enum的值。
当然,我认为,有可能在编译器中添加对 static 枚举(即只有静态项的枚举)的特殊支持,因此它会生成一些函数,返回枚举的值或带有它们的静态向量,但我认为编译器中额外的复杂性是不值得的。
如果确实需要此功能,可以编写一个自定义语法扩展(参见this问题).这个扩展应该接收一个标识符列表,并输出一个枚举和一个静态常量向量,这些标识符作为内容.一个常规的宏在某种程度上也会起作用,但据我所知,您不能用多重性转录宏参数两次,因此您必须手动编写两次枚举元素,这很不方便。
这个问题也可能引起一些兴趣:#5417
当然,你也可以编写代码,手工返回枚举元素的列表。

vd8tlhqk

vd8tlhqk4#

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

j2qf4p5b5#

可以使用关联的常量:

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

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

vsikbqxv6#

如果你不想导入一个第三方的板条箱,你可以自己做一个宏来实现,下面是我实现它的方法(可能有很多方法可以改进):

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中的简单枚举。

lndjwyie

lndjwyie7#

enum-iterator crate有助于迭代枚举器。

wh6knrhe

wh6knrhe8#

以下是我对“艾哈迈德·梅雷兹”回答的看法:

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

az31mfrm9#

我对“koral的回答”的变体

  • 不需要vis
  • 实现返回实际迭代器而不是数组的into_iter
macro_rules! iterable_enum {(
  $(#[$derives:meta])*
  $pub:vis enum $name:ident {
    $(
      $(#[$nested_meta:meta])*
      $member:ident,
    )*
  }) => {
    const _MEMBERS_COUNT:usize = iterable_enum!(@count $($member)*);
    $(#[$derives])*
    $pub enum $name {
      $($(#[$nested_meta])* $member),*
    }
    impl $name {
      pub fn into_iter() -> std::array::IntoIter<$name, _MEMBERS_COUNT> {
        [$($name::$member,)*].into_iter()
      }
    }
  };
  (@count) => (0usize);
  (@count  $x:tt $($xs:tt)* ) => (1usize + iterable_enum!(@count $($xs)*));
}

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

相关问题