Trait IteratorExt

Source
pub trait IteratorExt: Iterator {
    // Provided methods
    fn empty_fallback_chain<U>(
        self,
        other: U,
    ) -> EmptyFallbackChain<Self, U::IntoIter> 
       where Self: Sized,
             U: IntoIterator<Item = Self::Item> { ... }
    fn maybe_empty_fallback_chain<U>(
        self,
        other: Option<U>,
    ) -> EmptyFallbackChain<Self, U::IntoIter> 
       where Self: Sized,
             U: IntoIterator<Item = Self::Item> { ... }
}
Expand description

Trait for extending Iterators with methods to create EmptyFallbackChain iterators.

Provided Methods§

Source

fn empty_fallback_chain<U>( self, other: U, ) -> EmptyFallbackChain<Self, U::IntoIter>
where Self: Sized, U: IntoIterator<Item = Self::Item>,

Takes two iterators and creates a new iterator that runs through the second only if the first produces no output. Can take anything implementing IntoIterator as a second argument (as long as it produces an iterator over the same type).

empty_fallback_chain() will return a new iterator which will iterate over this (first) iterator - if it produces any values, then the second iterator is dropped. However, if the first iterator doesn’t produce any values, then the second iterator is iterated over instead (i.e. “as a fallback”).

In other words, it links two iterators in a chain, but only if the first is empty.

This also applies in reverse direction when iterating backwards (i.e. with DoubleEndedIterator::next_back, the second iterator is attempted to be iterated backwards first - only if nothing is emitted, is the first iterator then iterated backwards instead, else the first iterator is simply dropped).

§Examples

Basic usage:

use empty_fallback_chain::prelude::*;
let higher_priority = [1, 2, 3];
let lower_priority = [4, 5, 6];

let iter = higher_priority.into_iter().empty_fallback_chain(lower_priority.into_iter());
assert_eq!(iter.collect::<Vec<_>>(), vec![1, 2, 3]);

The major feature of EmptyFallbackChain is that if the first iterator produces no values, then the second iterator will be used instead.

use empty_fallback_chain::IteratorExt as _;

let higher_priority = [1, 3, 5];
let lower_priority = [10, 11, 78];

/// Filter for even numbers - no data in the higher priority iterator matches this,
/// so when the filtered version is used as the first half of an `EmptyFallbackChain`,
/// the "fallback" iterator is what's used.
fn even(v: &u32) -> bool {
    v % 2 == 0
}

let iter = higher_priority.into_iter().filter(even)
    .empty_fallback_chain(lower_priority.into_iter());
assert_eq!(iter.collect::<Vec<_>>(), vec![10, 11, 78]);

If the higher priority iterator produces any values, then the lower priority iterator is never used. For example, with a filter that doesn’t remove all of the higher-priority information:

use empty_fallback_chain::prelude::*;

let higher_priority = [1, 3, 5];
let lower_priority = [10, 11, 78];

fn incomplete_higher_filter(v: &u32) -> bool {
   *v != 3
}

let iter = higher_priority.into_iter().filter(incomplete_higher_filter)
    .empty_fallback_chain(lower_priority.into_iter());
assert_eq!(iter.collect::<Vec<_>>(), vec![1, 5]);

This can be used to create incredibly powerful, lazily evaluated, fallback systems. If you use multiple EmptyFallbackChain in sequence, you can create a sort of “iterator priority” construction.

use empty_fallback_chain::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Contact {
    pub name: &'static str,
    pub email: &'static str,
    pub pronouns: &'static str
}

// Example conditions
fn is_tuesday() -> bool { true }
fn is_wednesday() -> bool { false }
fn is_weekend() -> bool { false }

use Contact as C;
use core::iter as iter;

const bob: C = C::new("Bob Angie", "the-eponymous-bob@example.com", "he/him");
const tracey: C = C::new("Tracy Mill", "tracy-mill@corpo.example.com", "she/her");
const alan: C = C::new("Alan", "alanspace@example.com", "he/him");
const matriss: C = C::new("Matriss Karisle", "matriss@example.com", "they/them");
const charlie: C = C::new("Charlie Stone", "charlie-charlie@example.com", "she/her");
const harri: C = C::new("Harri", "harri-up@example.com", "they/she");
const mel: C = C::new("Mel", "mel@corpo.example.com", "she/her");
const troy: C = C::new("Troy", "helenofcity@example.com", "he/him");

// Define the contact lists as functions producing iterators, in order of preference.
// In reality, you'd use some better means of determining availability, but the principle
// of using fallbacks is sound, and can be used for many scenarios

fn emergency_contacts() -> impl Iterator<Item = Contact> {
    iter::empty()
        .chain(iter::once(troy).filter(|_| !is_tuesday() && !is_weekend()))
        .chain(iter::once(charlie).filter(|_| !is_weekend()))
        .chain([bob, matriss, harri].into_iter().filter(|_| !is_tuesday() &&
            !is_wednesday()))
}

fn distant_family_contacts() -> impl Iterator<Item = Contact> {
    // fill in here
}

fn corpo_contacts() -> impl Iterator<Item = Contact> {
    // fill in here
}

fn friendly_evening_contacts() -> impl Iterator<Item = Contact> {
    // fill in here
}

// Then, build contact scenarios, using `empty_fallback_chain`
// If there are no contacts available for emergency situations, then
// this will iterate for contacts who can just be messaged for a "friendly evening".
// If that fails, then it will iterate over all distant family contacts.
fn i_have_an_emergency() -> impl Iterator<Item = Contact> {
    emergency_contacts()
        .empty_fallback_chain(friendly_evening_contacts())
        .empty_fallback_chain(distant_family_contacts())

}

fn i_want_a_friendly_time() -> impl Iterator<Item = Contact> {
    friendly_evening_contacts()
        .empty_fallback_chain(distant_family_contacts())
        .empty_fallback_chain(corpo_contacts())
}

fn i_am_having_an_existential_crisis() -> impl Iterator<Item = Contact> {
    // fill in here
}
Source

fn maybe_empty_fallback_chain<U>( self, other: Option<U>, ) -> EmptyFallbackChain<Self, U::IntoIter>
where Self: Sized, U: IntoIterator<Item = Self::Item>,

Provide an empty fallback chain to this iterator, but only optionally so.

Should it be a common case that you wish to only provide an empty fallback sometimes but not others, then this function will let you efficiently create an EmptyFallbackChain where the second iterator can be set to empty easily by just passing None, equivalent to there being no fallback, or a fallback which produces no iterator items.

This creates less type-nesting and less indirection than using some other mechanisms to “optionally” iterate something. Note that should you wish to provide None directly as a parameter, you may need to use the ::<> turbofish syntax to manage type deductions - use any iterator that makes sense in the context here, as you will be setting it to None anyway.

§Basic Illustration
use empty_fallback_chain::IteratorExt as _;
use core::iter::Empty;

fn maybe_empty_fb<A: IntoIterator, B: IntoIterator<Item = A::Item>>(a: A, b: Option<B>) -> Vec<A::Item> {
    a.into_iter().maybe_empty_fallback_chain(b.map(IntoIterator::into_iter)).collect::<Vec<_>>()
}

assert_eq!(maybe_empty_fb([1,2,3], Some([4, 5, 6])), vec![1, 2, 3]);
assert_eq!(maybe_empty_fb::<_, Empty<usize>>([1,2,3], None), vec![1, 2, 3]);
assert_eq!(maybe_empty_fb([], Some([4, 5, 6])), vec![4, 5, 6]);
assert_eq!(maybe_empty_fb::<_, Empty<usize>>([], None), vec![]);
§Conditionally Providing Fallback
use empty_fallback_chain::{IteratorExt as _, EmptyFallbackChain};
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![]);

Implementors§