【发布时间】:2021-11-16 18:54:52
【问题描述】:
我想在 Rust 中使用迭代器作为全局状态。简化示例:
static nums = (0..).filter(|&n|n%2==0);
这可能吗?
【问题讨论】:
我想在 Rust 中使用迭代器作为全局状态。简化示例:
static nums = (0..).filter(|&n|n%2==0);
这可能吗?
【问题讨论】:
你可以做到,但你必须在此过程中与语言作斗争。
首先,使用 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 之外还实现了 Send 和 Sync。
结果使用如下:
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]
);
}
【讨论】:
没有。出于多种原因:
statics 必须显式键入。在这种情况下,类型还是比较简单的:core::iter::Filter<core::ops::RangeFrom<i32>, fn(&i32) -> bool>。Iterator 的主要方法next 需要一个&mut self 参数。 statics 默认情况下不能可变,因为这不安全。static 的值必须是常量表达式。您的初始化程序不是常量表达式。【讨论】:
get_next_id = itertools.count().__next__ 的全局变量在 Python 中并不罕见。
next_id 示例只是说明全局迭代器如何可能有用。