简体   繁体   中英

Can I use an iterator in global state in Rust?

I want to use an iterator as global state in Rust. Simplified example:

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

Is this possible?

No. For multiple reasons:

  1. Iterators types tend to be complicated. This is usually not a problem because iterator types must rarely be named, but static s must be explicitly typed. In this case the type is still relatively simple: core::iter::Filter<core::ops::RangeFrom<i32>, fn(&i32) -> bool> .
  2. Iterator 's main method, next , needs a &mut self parameter. static s can't be mutable by default, as this would not be safe.
  3. Iterators can only be iterated once. Therefore it makes little sense to have a global iterator in the first place.
  4. The value to initialize a static must be a constant expression. Your initializer is not a constant expression.

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 are read-only. This is because Rust is multi-threaded, and allowing writing to a static would constitute a data race. Iterator's next() method takes &mut self , which makes sense, as it must update the underlying counter. To advance your global iterator, it must be wrapped in a Mutex .

With those out of the way, we can take a look at the 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)));
}

We use lazy_static to implement the create-on-first-use idiom: the first time NUM is accessed, it will create the iterator.

The iterator itself is boxed because we have to spell out the type. As mcarton points out, you could out the type as Filter<RangeFrom<i32>, fn(&i32) -> bool> , but it'd be closely tied to the implementation, and you'd have to change it as soon as you switch to a different combinator. To avoid the hassle it's better to box the iterator, which erases the actual type used to implement it. (Erasing the type involves dynamic dispatch, but so would specifying the filter function through a function pointer.) The type-erased iterator must be marked as Send and Sync to be usable from multiple threads.

Finally, we wrap the iterator in a Mutex , so we can safely advance it. The result is used as follows:

fn main() {
    assert_eq!(
        Vec::from_iter(NUMS.lock().unwrap().by_ref().take(5)),
        vec![0, 2, 4, 6, 8]
    );
    assert_eq!(
        Vec::from_iter(NUMS.lock().unwrap().by_ref().take(5)),
        vec![10, 12, 14, 16, 18]
    );
}

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM