Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the most efficient way to reuse an iterator in Rust?

I'd like to reuse an iterator I made, so as to avoid paying to recreate it from scratch. But iterators don't seem to be cloneable and collect moves the iterator so I can't reuse it.

Here's more or less the equivalent of what I'm trying to do.

let my_iter = my_string.unwrap_or("A").chars().flat_map(|c|c.to_uppercase()).map(|c| Tag::from(c).unwrap() );
let my_struct = {
  one: my_iter.collect(),
  two: my_iter.map(|c|{(c,Vec::new())}).collect(),
  three: my_iter.filter_map(|c|if c.predicate(){Some(c)}else{None}).collect(),
  four: my_iter.map(|c|{(c,1.0/my_float)}).collect(),
  five: my_iter.map(|c|(c,arg_time.unwrap_or(time::now()))).collect(),
  //etc...
}
like image 644
Camden Narzt Avatar asked Feb 15 '16 05:02

Camden Narzt


People also ask

Can iterators be reused?

iterators are not reusable; you need to get a fresh Iterator from the Iterable collection each time you want to iterate over the elements.

Are Rust iterators lazy?

In Rust, iterators are lazy, meaning they have no effect until you call methods that consume the iterator to use it up. For example, the code in Listing 13-10 creates an iterator over the items in the vector v1 by calling the iter method defined on Vec<T> . This code by itself doesn't do anything useful.

How do you consume an iterator in Rust?

fn last(self) -> Option<Self::Item> Consumes the iterator, returning the last element. This method will evaluate the iterator until it returns None . While doing so, it keeps track of the current element. After None is returned, last() will then return the last element it saw.

What does collect () do in Rust?

collect() can take all the values in an Iterator 's stream and stick them into a Vec . And the map method is now generating Result<i32, &str> values, so everything lines up.


2 Answers

You should profile before you optimize something, otherwise you might end up making things both slower and more complex than they need to.

The iterators in your example

let my_iter = my_string.unwrap_or("A").chars().flat_map(|c|c.to_uppercase()).map(|c| Tag::from(c).unwrap() );

are thin structures allocated on the stack. Cloning them isn't going to be much cheaper than building them from scratch.

Constructing an iterator with .chars().flat_map(|c| c.to_uppercase()) takes only a single nanosecond when I benchmark it.

According to the same benchmark, wrapping iterator creation in a closure takes more time than simply building the iterator in-place.

Cloning a Vec iterator is not much faster than building it in-place, both are practically instant.

test construction_only    ... bench:           1 ns/iter (+/- 0)
test inplace_construction ... bench:         249 ns/iter (+/- 20)
test closure              ... bench:         282 ns/iter (+/- 18)
test vec_inplace_iter     ... bench:           0 ns/iter (+/- 0)
test vec_clone_iter       ... bench:           0 ns/iter (+/- 0)
like image 63
ArtemGr Avatar answered Oct 14 '22 00:10

ArtemGr


Iterators in general are Clone-able if all their "pieces" are Clone-able. You have a couple of them in my_iter that are not: the anonymous closures (like the one in flat_map) and the ToUppercase struct returned by to_uppercase.

What you can do is:

  1. rebuild the whole thing (as @ArtemGr suggests). You could use a macro to avoid repetition. A bit ugly but should work.
  2. collect my_iter into a Vec before populating my_struct (since you seem to collect it anyway in there): let my_iter: Vec<char> = my_string.unwrap_or("A").chars().flat_map(|c|c.to_uppercase()).map(|c| Tag::from(c).unwrap() ).collect();
  3. create your own custom iterator. Without your definitions of my_string (since you call unwrap_or on it I assume it's not a String) and Tag it's hard to help you more concretely with this.
like image 22
Paolo Falabella Avatar answered Oct 13 '22 23:10

Paolo Falabella