mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-27 09:14:20 +00:00
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. |
||
---|---|---|
.. | ||
adapters | ||
traits | ||
mod.rs | ||
range.rs | ||
sources.rs |