Are side-effects of the closure in `Iterator::inspect` well defined so it can be used for e.g. counting?
Asked Answered
C

1

6

I have an iterator and I would like to fold it with a nice method (say Iterator::sum):

let it = ...;
let sum = it.sum::<u64>();

Then I notice that I also need to know the number of elements in the iterator. I could write a for loop and do the counting and summing up manually, but that's not nice since I have to change a potentially long iterator adapter chain and all of that. Additionally, in my real code I'm not using sum, but a more complex "folding method" which logic I don't want to replicate.

I had the idea to (ab)use Iterator::inspect:

let it = ...;
let mut count = 0;
let sum = it.inspect(|_| count += 1).sum::<u64>();

This works, but does it work by coincidence or is this behavior guaranteed? The documentation of inspect mentions that the closure is called for each element, but also states that it's mostly used as debugging tool. I'm not sure if using it this way in production code is a good idea.

Cranberry answered 13/1, 2019 at 17:54 Comment(1)
i don't think it is different from this code let sum = it.map(|x| {count += 1; x}).sum::<u64>();Porterfield
C
5

I'd say it's guaranteed, but you'll never find it explicitly stated as such. As you mention, the documentation states:

Do something with each element of an iterator, passing the value on.

Since the function guarantees to run the closure for each element, and the language guarantees what happens when a closure is run (by definition of a closure), the behavior is safe to rely on.

That being said, once you have one or more side-effects, it might be better to eschew heavy chaining and move to a boring for loop for readability, but that will depend on the exact case.

Clarendon answered 13/1, 2019 at 18:30 Comment(3)
I'd agree with that. It seems like bad style to rely on side-effects like that, except for perhaps debugging or a quick fix before restructuring the code.Uturn
@PeterHall Putting count += 1 inside of a for loop is still a side-effect, so ultimately it's "all the same". Rust even added Iterator::for_each which feels incorrect to me, but it is a pragmatic option.Clarendon
It's really not the same. In a for loop, you build an iterator as an expression and then consume it, possibly with side effects. Using inspect gives you side effects from arbitrary points within the expression that builds the iterator. The loop is idiomatic but most would not consider inspect to be so.Uturn

© 2022 - 2024 — McMap. All rights reserved.