Consider a situation where you want to iterate over a list (or some other iterable data structure), but in a programmatically-defined direction. Specifically, in a direction defined by some enumerated type.
enum Flag { A, B, } let flag: Flag = /* ... */ match flag { Flag::A => { for elem in list.iter() { /* lots of complicated logic... */ } }, Flag::B => { for elem in list.iter().rev() { /* the same complicated logic... */ } } }
The most obvious issue here is that this code isn't very DRY. Imagine that the bodies of each for
loop are both:
So, let's refactor our current code by extracting the iterator out. This way we won't have duplicate loops.
let my_iter = match flag { Flag::A => list.iter(), Flag::B => list.iter().rev(), } for elem in my_iter { /* lots of complicated logic... */ }
This makes sense, right? Well, the issue here is that this won't compile! Note that the arms of our match
have differing types, which is not allowed (hint: think about what type you'd assign my_iter
if you were to annotate this explicitly).
This is where itertools::Either
saves the day (my Rust code is starting to become a Haskell DSL at this point). Now, we can write this:
let my_iter = match flag { Flag::A => Either::Left(list.iter()), Flag::B => Either::Right(list.iter().rev()), } for elem in my_iter { /* lots of complicated logic... */ }
This code does compile and works as you'd expect.
Still, perhaps you're having the same thoughts that I had when I wrote this and had everything magically work: how does the compiler know that my_iter
implements IntoIter
? Well, this is where Rust's extremely expressive type system truly shines.
If we consult the actual code for itertools::Either
:
/// Convert the inner value to an iterator. /// /// ``` /// use either::*; /// /// let left: Either<_, Vec<u32>> = Left(vec![1, 2, 3, 4, 5]); /// let mut right: Either<Vec<u32>, _> = Right(vec![]); /// right.extend(left.into_iter()); /// assert_eq!(right, Right(vec![1, 2, 3, 4, 5])); /// ``` pub fn into_iter(self) -> Either<L::IntoIter, R::IntoIter> where L: IntoIterator, R: IntoIterator<Item = L::Item>, { match self { Left(l) => Left(l.into_iter()), Right(r) => Right(r.into_iter()), } }
Isn't it an object of pure beauty? Once again, you can always just pattern match your way to victory.