在 Rust 中,有没有办法遍历枚举的值?

Posted

技术标签:

【中文标题】在 Rust 中,有没有办法遍历枚举的值?【英文标题】:In Rust, is there a way to iterate through the values of an enum? 【发布时间】:2014-02-17 17:47:11 【问题描述】:

我来自 Java 背景,我可能有类似 enum Direction NORTH, SOUTH, EAST, WEST 的东西,我可以使用增强的 for 循环依次对每个值执行一些操作,例如:

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

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

【问题讨论】:

有an issue about an Enum trait 可以派生出类似for dir in Direction::values() ... 的东西(也许)。 【参考方案1】:

这是我对@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://***.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);
    

【讨论】:

【参考方案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!();
    

【讨论】:

【参考方案3】:

如果您不想导入第三方 crate,您可以制作自己的宏来执行此操作。这是我实现它的方法(可能有改进的方法):

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

【讨论】:

向量的堆分配真的没有必要。【参考方案4】:

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

另见:

What is the correct way to return an Iterator (or any other trait)? Why can I return a reference to a local literal but not a variable?

【讨论】:

这实际上似乎可能是比使用 rust 中的“枚举”来满足您的需要更好的选择。我实际上想知道车辙是否会更好地调用他们的枚举、工会或其他东西。此外,作为一个强大的变体,您可以关联其他信息,例如:[North(0), South(180), EAST(90), WEST(270)] 等,或者更多给定元组帮助... 这种方法的问题是您需要使数组与枚举常量保持同步。如果你添加一个常量而忘记调整iterator() 方法,你就会有一个bug。 strum(或类似的库)解决了这个问题。【参考方案5】:

您可以使用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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

enum Option<T> 
    None,
    Some(T)

?

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

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

所以没有理智的方法可以迭代 any 枚举的值。

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

如果你真的想要这个功能,你可以编写一个自定义语法扩展(参见this 问题)。这个扩展应该接收一个标识符列表并输出一个枚举和一个带有这些标识符的静态常量向量作为内容。常规宏在某种程度上也可以工作,但据我所知,您不能两次转录具有多重性的宏参数,因此您必须手动编写两次枚举元素,这很不方便。

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

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

【讨论】:

这很有帮助。我认为对我来说最简单的事情可能是编写一个我的枚举的 impl,它返回一个值列表或一个迭代器或其他东西。谢谢! 我非常不同意枚举枚举不值得。这是一件常见且有用的事情,这就是为什么它们被称为枚举!当然,代数能力很棒,但最常见的情况也很方便。 @RexKerr,这只是我关于为什么这个功能仍然不存在的原因的建议。我相信,相应问题中的停止讨论支持我的观点。顺便说一句,当我需要一次对所有枚举元素做某事时,它通常是某种索引(例如哈希映射),它允许通过某个键检索枚举值,并且无论如何都应该手动构建这个索引。 @VladimirMatveev - 嗯,很少应该手动构建可以自动构建的。有编译器有点意思,不是吗?但我同意你的分析。 这是值得的。并且实例化的枚举 Option 可以很好地迭代。

以上是关于在 Rust 中,有没有办法遍历枚举的值?的主要内容,如果未能解决你的问题,请参考以下文章

如何遍历 Hashmap、打印键/值并删除 Rust 中的值?

hashmap - 如何遍历Hashmap,打印键/值并删除Rust中的值?

有没有办法在 Rust 中通过枚举来索引数组?

如何在java中遍历枚举[重复]

C# 遍历枚举

循环遍历预定义的值