5 stable releases
2.0.0 | Jan 19, 2025 |
---|---|
1.1.0 | Jan 19, 2025 |
1.0.2 | Jan 18, 2025 |
1.0.1 | Jul 2, 2024 |
1.0.0 | Jul 1, 2024 |
#658 in Algorithms
347 downloads per month
Used in nstree
40KB
382 lines
empty-fallback-chain
Simple rust library that provides an iterator-combinator - similar to Chain
- that takes two iterators and glues them together, one after the other. However, unlike that combinator, this one will only run through the second iterator if the first iterator never produces any value. To get started, see IteratorExt::empty_fallback_chain
.
This is highly useful when you have a collection of iterators and want to take values from the first one that produces something. The combinator in this library is guaranteed not to attempt to read any values from the second iterator unless the first iterator produces nothing, so it is very good at being lazy.
Basic Example
use empty_fallback_chain::IteratorExt as _;
fn empty_fallback(first: impl IntoIterator<Item = usize>, second: impl IntoIterator<Item = usize>) -> Vec<usize> {
first.into_iter().empty_fallback_chain(second).collect::<Vec<_>>()
}
assert_eq!(empty_fallback([1, 2, 3], [4, 5, 6]), vec![1, 2, 3]);
assert_eq!(empty_fallback([], [4, 5, 6]), vec![4, 5, 6]);
Basic Conditional Fallback Example
This illustrates that it's relatively easy to also conditionally apply fallback, with a unified iterator type lacking unnecessary indirection.
use empty_fallback_chain::IteratorExt as _;
use core::iter;
fn apply_fallback_conditionally(
a: impl IntoIterator<Item = usize>,
do_fallback: bool
) -> impl Iterator<Item = usize> {
if do_fallback {
a.into_iter().empty_fallback_chain([3, 4, 5].iter().copied())
} else {
// Note here that we need to provide some help with deduction by providing the
// outermost iterator of the chain as a hint, because `maybe_empty_fallback_chain`
// takes an `IntoIterator` as it's parameter and then uses the associated
// `IntoIter` type, rather than only taking `Iterator`s
//
// Another way to solve this problem is to only use one call to
// `maybe_empty_fallback_chain`, and unify the `Some` and `None` cases into a
// single `Option` beforehand.
a.into_iter().maybe_empty_fallback_chain::<iter::Copied<_>>(None)
}
}
assert_eq!(apply_fallback_conditionally([1, 2], true).collect::<Vec<_>>(), vec![1, 2]);
assert_eq!(apply_fallback_conditionally([], true).collect::<Vec<_>>(), vec![3, 4, 5]);
assert_eq!(apply_fallback_conditionally([1, 2], false).collect::<Vec<_>>(), vec![1, 2]);
assert_eq!(apply_fallback_conditionally([], false).collect::<Vec<_>>(), vec![]);
Performance & Optimisation Notes
The code in this library also takes a lot of implementation infrastructure from inside the Standard Library's implementation of Chain
. This is important, because - especially with nested chaining - it would be very easy to accidentally create very challenging-to-optimise call chains.
By using the work the Rust Standard Library Maintainers have already done to make their own implementation "optimiser friendly", we can hopefully avoid many performance pitfalls.
Future Possibilities
In future, it might be possible to implement more powerful features, like the ability to do some kind of interesting threshold chaining where you can pick the first "k" of "n" iterators.
The main way I can see to do this is to make some sort of macro that creates a structure with a threshold counter. But that's not the focus right now, and this library will likely reach some sort of "complete" status in very near future unless there is a pressing need for this functionality.