【问题标题】:Can I use an iterator in global state in Rust?我可以在 Rust 的全局状态中使用迭代器吗?
【发布时间】:2021-11-16 18:54:52
【问题描述】:

我想在 Rust 中使用迭代器作为全局状态。简化示例:

static nums = (0..).filter(|&n|n%2==0);

这可能吗?

【问题讨论】:

    标签: rust static


    【解决方案1】:

    你可以做到,但你必须在此过程中与语言作斗争。

    首先,使用 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 用于实现 create-on-first-use 习惯用法以解决非常量初始值。第一次访问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]
        );
    }
    

    Playground

    【讨论】:

      【解决方案2】:

      没有。出于多种原因:

      1. 迭代器类型往往很复杂。这通常不是问题,因为迭代器类型必须很少被命名,但 statics 必须显式键入。在这种情况下,类型还是比较简单的:core::iter::Filter&lt;core::ops::RangeFrom&lt;i32&gt;, fn(&amp;i32) -&gt; bool&gt;
      2. Iterator 的主要方法next 需要一个&amp;mut self 参数。 statics 默认情况下不能可变,因为这不安全。
      3. 迭代器只能迭代一次。因此,一开始就拥有一个全局迭代器是没有意义的。
      4. 初始化static 的值必须是常量表达式。您的初始化程序不是常量表达式。

      【讨论】:

      • 第 3 点不一定正确——如果迭代器是无限的,那么全局迭代器就很有意义。例如,定义为 get_next_id = itertools.count().__next__ 的全局变量在 Python 中并不罕见。
      • 如果您的目标是创建一个每次使用时都会递增的对象,那么 Python 的方式就不是可行的方法。在 Rust 中,我会使用 something like this。虽然我还是宁愿传递一个状态对象,而不是依赖一个全局对象。
      • 请注意,评论是我的,而不是来自 OP。我只能推测 OP 的实际需求,但我的 next_id 示例只是说明全局迭代器如何可能有用。
      猜你喜欢
      • 2021-07-19
      • 2016-05-23
      • 2011-03-26
      • 1970-01-01
      • 2021-06-06
      • 2016-06-06
      • 1970-01-01
      • 2013-11-05
      • 1970-01-01
      相关资源
      最近更新 更多