6c943bad02
Refactor iteration logic in the `Flatten` and `FlatMap` iterators The `Flatten` and `FlatMap` iterators both delegate to `FlattenCompat`: ```rust struct FlattenCompat<I, U> { iter: Fuse<I>, frontiter: Option<U>, backiter: Option<U>, } ``` Every individual iterator method that `FlattenCompat` implements needs to carefully manage this state, checking whether the `frontiter` and `backiter` are present, and storing the current iterator appropriately if iteration is aborted. This has led to methods such as `next`, `advance_by`, and `try_fold` all having similar code for managing the iterator's state. I have extracted this common logic of iterating the inner iterators with the option to exit early into a `iter_try_fold` method: ```rust impl<I, U> FlattenCompat<I, U> where I: Iterator<Item: IntoIterator<IntoIter = U>>, { fn iter_try_fold<Acc, Fold, R>(&mut self, acc: Acc, fold: Fold) -> R where Fold: FnMut(Acc, &mut U) -> R, R: Try<Output = Acc>, { ... } } ``` It passes each of the inner iterators to the given function as long as it keep succeeding. It takes care of managing `FlattenCompat`'s state, so that the actual `Iterator` methods don't need to. The resulting code that makes use of this abstraction is much more straightforward: ```rust fn next(&mut self) -> Option<U::Item> { #[inline] fn next<U: Iterator>((): (), iter: &mut U) -> ControlFlow<U::Item> { match iter.next() { None => ControlFlow::CONTINUE, Some(x) => ControlFlow::Break(x), } } self.iter_try_fold((), next).break_value() } ``` Note that despite being implemented in terms of `iter_try_fold`, `next` is still able to benefit from `U`'s `next` method. It therefore does not take the performance hit that implementing `next` directly in terms of `Self::try_fold` causes (in some benchmarks). This PR also adds `iter_try_rfold` which captures the shared logic of `try_rfold` and `advance_back_by`, as well as `iter_fold` and `iter_rfold` for folding without early exits (used by `fold`, `rfold`, `count`, and `last`). Benchmark results: ``` before after bench_flat_map_sum 423,255 ns/iter 414,338 ns/iter bench_flat_map_ref_sum 1,942,139 ns/iter 2,216,643 ns/iter bench_flat_map_chain_sum 1,616,840 ns/iter 1,246,445 ns/iter bench_flat_map_chain_ref_sum 4,348,110 ns/iter 3,574,775 ns/iter bench_flat_map_chain_option_sum 780,037 ns/iter 780,679 ns/iter bench_flat_map_chain_option_ref_sum 2,056,458 ns/iter 834,932 ns/iter ``` I added the last two benchmarks specifically to demonstrate an extreme case where `FlatMap::next` can benefit from custom internal iteration of the outer iterator, so take it with a grain of salt. We should probably do a perf run to see if the changes to `next` are worth it in practice. |
||
---|---|---|
.. | ||
fmt | ||
hash | ||
iter | ||
num | ||
ops | ||
alloc.rs | ||
any.rs | ||
array.rs | ||
ascii.rs | ||
asserting.rs | ||
atomic.rs | ||
bool.rs | ||
cell.rs | ||
char.rs | ||
clone.rs | ||
cmp.rs | ||
const_ptr.rs | ||
convert.rs | ||
future.rs | ||
intrinsics.rs | ||
lazy.rs | ||
lib.rs | ||
macros.rs | ||
manually_drop.rs | ||
mem.rs | ||
nonzero.rs | ||
ops.rs | ||
option.rs | ||
pattern.rs | ||
pin_macro.rs | ||
pin.rs | ||
ptr.rs | ||
result.rs | ||
simd.rs | ||
slice.rs | ||
str_lossy.rs | ||
str.rs | ||
task.rs | ||
time.rs | ||
tuple.rs | ||
unicode.rs | ||
waker.rs |