How to collect... nothing?

Is there a way to turn this:

let items = vec![1,2,3];

for item in items {
process(item)
}
into this:

items.iter().map(|item| { process(item) })

where process doesn't return anything?

the latter does nothing (iterators are lazy), and adding collect makes compiler complain about missing type information or inability to collect list of units.
Is there some other way of consuming it?

1 Like

First of all, the direct equivalent would be items.into_iter(), not items.iter(). Secondly, it is considered idiomatic to use a for loop in this case, not to try and cludge an iterator chain to make it work.

 

... which you can do by calling anything that consumes the iterator. Like .count(). But that's not exactly clear about what you're trying to do. Or you could use .collect::<Vec<_>>() (it's perfectly valid to collect units), which is less opaque, but still weird.

Finally, if you're using itertools, there's Itertools::foreach, which is pretty much what you want.

3 Likes

Thanks, foreach is what I want (and I hope it will get into stdlib eventually).

As for being idiomatic: I disagree. For me this:
for item in items.skip(3).filter(...).map(...) {
process(item);}
}
looks much worse then:

items.skip(3)
.filter(...)
.map(...)
foreach(...)

and is harder to modify.

1 Like

Well if I remember correctly, something similar has already been knocked back from the stdlib, because it was considered not idiomatic. So, you might be waiting a while...

Not directly related, but there is a for_each method in the Stream trait of futures-rs (which you could see as the non-blocking equivalent of Iterator), and it feels pretty natural to use. What was the reason for not having foreach on Iterator?

As for being idiomatic: I disagree. For me this:

for item in items.skip(3).filter(...).map(...) {
    process(item);
}

looks much worse than:

items.skip(3)
     .filter(...)
     .map(...)
     .foreach(...)

and is harder to modify.

idiomaticity has nothing to do with optical quality. Something is idiomatic if it's the way it's meant to be written in a language. Iterators in Rust are meant to lazily produce values. Iteration itself is not a part of iterators, providing values for an iteration is.

Also in my own opinion the following looks more readable:

for item in items.skip(3) {
    if your_filter { continue; }
    let item = your_map(item);
    process(item);
}
2 Likes

Optical quality is not the main reason to prefer one solution over another here.
Other important things:

  1. You don't need to switch mental model of your code. If you have set of transformations and you want to add/modify/remove one, you don't need to do things differently depending on what kind of transformation it is.
    This is very important for me. More then visual side, though that matters a lot too.

  2. Composability. You can compose transformation by adding/removing them depending on your needs.
    It is easier to do if they are all working in the same way.

Oh wow...I find this way less readable than using iterator transformations.

I find for_each to be the ideal choice for situations like this, rather than storing the collection into a value and iterating through it. It's smaller, simpler, and you don't leave some temporary value around just to consume it.

We decide what is "idiomatic," and certainly readability (or "optical quality") is something we should value when making that choice.

Its also not true that the iterator adapters all produce values lazily. Quite a few adapters are consumers (collect, count, fold, sum, last, nth, et cetera) which perform the iteration. foreach would just be another consuming method.

1 Like

You can even approximate foreach with fold((), |_, x| /* ... */)
(but surely that fails readability...)

1 Like

We decide what is "idiomatic," and certainly readability (or "optical quality") is something we should value when making that choice.

of course, that is how something is made idiomatic, but whether something is idiomatic does not relate to the readability.

All of these adapters exist so that the user does not need to create a temporary mutable variable (+ saves boilerplate that could include bugs). foreach would be the only one that does not require any boilerplate when written by hand and that doesn't require a mutable variable or recursion (+ tail call elimination).

My way is indeed less readable when the filter argument is not a closure but just a name/path. But when you insert closures that aren't just a trivial |x| x.function(y), there's a good chance my way is more readable. It's case by case.

Remember:

Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it? --Brian Kernighan

1 Like

if I mutably iterate a container and modify the elements in the mapped function, I wouldn't consider it a sideeffect. Why is there a mutable iterator but no dedicated way to iterate? It's a strange design decision.

E.g. Elixir has lazy streams with all that functional fold, map... stuff and although it doesn't have mutability at all, streams have a run() function to consume them for their sideeffects.