我能否在Rust的全局状态中使用迭代器?

3
我想在 Rust 中使用迭代器作为全局状态。以下是一个简化的示例:
static nums = (0..).filter(|&n|n%2==0);

这可能吗?
2个回答

4
你能做到这点,但途中你必须与语言抗争。首先,使用 `static` 声明创建的真正 Rust 静态常量需要是编译时常量。所以类似 `static FOO: usize = 10` 的东西可以编译,但是 `static BAR: String = "foo".to_string()` 不能,因为 `BAR` 需要运行时分配。虽然您的迭代器不需要运行时分配(尽管使用它将使您的生活更加简单,稍后您将看到),但其类型足够复杂,不支持编译时初始化。 其次,Rust 静态变量需要事先指定完整类型。对于任意迭代器来说,这是个问题,人们想通过组合迭代器适配器和闭包来创建。虽然在这种特殊情况下,如 mcarton 所指出的那样,一个人可以将类型指定为 `Filter<RangeFrom<i32>, fn(&i32) -> bool>`,但它会与当前实现紧密耦合。当您切换到其他组合器时,您必须更改该类型。为了避免麻烦,最好将迭代器隐藏在 dyn Iterator 引用后面,即通过将其放入 Box 中来擦除类型。擦除类型涉及动态调度,但通过函数指针指定过滤函数也会这样做。第三,Rust 静态变量是只读的,并且 Iterator::next() 接受 &mut self,因为它更新迭代的状态。静态必须是只读的,因为 Rust 是多线程的,而不证明没有读者或其他编写器的情况下编写静态将允许数据竞争在安全代码中。因此,要推进您的全局迭代器,您必须将其包装在 Mutex 中,它提供了线程安全性和内部可变性。在漫长的介绍之后,让我们来看一下相当短的实现:
use lazy_static::lazy_static;
use std::sync::Mutex;

lazy_static! {
    static ref NUMS: Mutex<Box<dyn Iterator<Item = u32> + Send + Sync>> =
        Mutex::new(Box::new((0..).filter(|&n| n % 2 == 0)));
}

lazy_static用于实现“第一次使用时创建”的惯用语,以解决非常量初始值的问题。当第一次访问NUMS时,它将创建迭代器。

如上所述,迭代器本身被装箱并包装在一个Mutex中。由于全局变量可能被多个线程访问,我们的装箱迭代器除了实现Iterator外还实现了SendSync

结果的使用如下:

fn main() {
    assert_eq!(NUMS.lock().unwrap().next(), Some(0)); // take single value
    assert_eq!(
        // take multiple values
        Vec::from_iter(NUMS.lock().unwrap().by_ref().take(5)),
        vec![2, 4, 6, 8, 10]
    );
}

游乐场


1
不行。原因有以下几点:
1. 迭代器类型往往比较复杂。通常这不是问题,因为迭代器类型很少需要命名,但是静态变量必须明确指定类型。在这种情况下,类型仍然相对简单:core::iter::Filter<core::ops::RangeFrom<i32>, fn(&i32) -> bool>
2. 迭代器的主要方法next需要一个&mut self参数。静态变量默认情况下不能是可变的,因为这不安全。
3. 迭代器只能被迭代一次。因此,首先拥有全局迭代器意义不大。
4. 初始化static所需的值必须是常量表达式。您的初始值不是常量表达式。

第三点不一定正确 - 如果迭代器是无限的,全局迭代器就有很多意义。例如,在Python中定义为get_next_id = itertools.count().__next__的全局变量并不罕见。 - user4815162342
如果您的目标是创建一个在每次使用时递增的对象,则Pythonic的方法不适用。在Rust中,我会使用类似于这样的东西。虽然我仍然更喜欢传递状态对象,而不是依赖全局变量。 - mcarton
只是要注意这个评论是我的,而不是原帖的。对于原帖的实际需求,我只能进行推测,但是我的 next_id 示例只是说明全局迭代器可能是一个有用的东西。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接