I want to use an iterator as global state in Rust. Simplified example:
static nums = (0..).filter(|&n|n%2==0);
Is this possible?
I want to use an iterator as global state in Rust. Simplified example:
static nums = (0..).filter(|&n|n%2==0);
Is this possible?
You can do it, but you'll have to fight the language along the way.
First, true Rust statics created with the static declaration need to be compile-time constants. So something like static FOO: usize = 10 will compile, but static BAR: String = "foo".to_string() won't, because BAR requires a run-time allocation. While your iterator doesn't require a run-time allocation (though using it will make your life simpler, as you'll see later), its type is complex enough that it doesn't support compile-time initialization.
Second, Rust statics require specifying the full type up-front. This is a problem for arbitrary iterators, which one would like to create by combining iterator adapters and closures. While in this particular case, as mcarton points out, one could specify the type as Filter<RangeFrom<i32>, fn(&i32) -> bool>, it'd be closely tied to the current implementation. You'd have to change the type as soon as you switch to a different combinator. To avoid the hassle it's better to hide the iterator behind a dyn Iterator reference, i.e. type-erase it by putting it in a Box. Erasing the type involves dynamic dispatch, but so would specifying the filter function through a function pointer.
Third, Rust statics are read-only, and Iterator::next() takes &mut self, as it updates the state of the iteration. Statics must be read-only because Rust is multi-threaded, and writing to a static without proof that there are no readers or other writers would allow a data race in safe code. So to advance your global iterator, you must wrap it in a Mutex, which provides both thread safety and interior mutability.
After the long introduction, let's take a look at the fairly short implementation:
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 is used to implement the create-on-first-use idiom to work around the non-const initial value. The first time NUMS is accessed, it will create the iterator.
As explained above, the iterator itself is boxed and wrapped in a Mutex. Since global variables are assumed to be accessed from multiple threads, our boxed iterator implements Send and Sync in addition to Iterator.
The result is used as follows:
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]
);
}
No. For multiple reasons:
statics must be explicitly typed. In this case the type is still relatively simple: core::iter::Filter<core::ops::RangeFrom<i32>, fn(&i32) -> bool>.Iterator's main method, next, needs a &mut self parameter. statics can't be mutable by default, as this would not be safe.static must be a constant expression. Your initializer is not a constant expression.