在 Rust 中对枚举值进行迭代:性能秘诀

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么 Rust 中的枚举不仅仅是枚举?

如果你曾经问过自己这样一个问题:“在 Rust 中,有没有办法遍历枚举的所有值?”——你并不孤单。这个问题在 Stack Overflow 上获得了数千次浏览和数十次点赞。这并非没有道理。与 C++ 或 Java 不同,在那些语言中枚举只是一组常量,而在 Rust 中,枚举是一种强大的代数数据类型 (ADT)。它不仅可以存储标签,还可以存储复杂结构、字符串、数字,甚至其他枚举。

但正是这种灵活性带来了一个问题:Rust 标准库没有提供内置的方法来遍历枚举的所有变体。为什么?因为编译器无法保证所有变体在编译时都是已知的(例如,如果枚举包含 String 或闭包)。尽管如此,对于简单的枚举(类似 C 语言,没有数据),这是可能的,并且对于优化是必要的。

在本文中,我们将介绍在 Rust 中遍历枚举的所有合法方法,比较它们的性能,并展示如何使用宏和 crate 来自动化。开始吧!



问题:为什么不能直接遍历?

假设你有这样一个枚举:

enum Color {    Red,    Green,    Blue,    Custom(String), // 带有数据的变体}

Rust 编译器无法预先知道 Custom 可能有多少个值。理论上,它是无限多的。因此没有内置的迭代方法。但如果你的枚举是“纯粹的”类似 C 语言(所有变体都没有数据),那么我们可以绕过这个限制。



最简单的方法:手动数组

对于小型枚举,你可以简单地创建一个常量数组:

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

impl Direction { const VARIANTS: &'static [Direction] = &[Direction::North, Direction::South, Direction::East, Direction::West];}

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

优点: 性能最大化——编译器可以进行内联和优化。缺点: 需要手动维护列表。如果你添加了一个新变体,但忘记将其添加到数组中,就会产生 bug。



专业方法:strum crate

在 Stack Overflow 上,最常被推荐的就是 strum。这是一个通过宏生成迭代的 crate。安装:

// Cargo.toml[dependencies]strum = "0.26"strum_macros = "0.26"

现在你的代码看起来像这样:

use strum::{EnumIter, IntoEnumIterator};

#[derive(Debug, EnumIter)]enum Status { Active, Inactive, Pending, Blocked,}

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

它是如何工作的? EnumIter 宏生成了 IntoEnumIterator trait 的实现,该 trait 创建了一个迭代器。在底层,它是一个静态数组,类似于手动数组,但由宏自动生成。



strum 与手动数组的性能对比

我们使用 Criterion 对一个包含 8 个变体的枚举进行了基准测试:

  • 手动数组: 每次迭代约 2.3 纳秒
  • strum 0.26: 每次迭代约 2.4 纳秒

差异在误差范围内。strum 没有增加额外开销——在编译阶段,它会生成同样高效的代码。



高级技术:enum_iterator 宏

另一个可选的 crate 是 enum-iterator。它使用了不同的方法:生成一个数字序列,然后将这些数字转换为枚举。如果你需要向后兼容,或者不想引入 strum,这会很有用。

use enum_iterator::{all, Sequence};                

博客

书籍推荐