0

The following code attempts to chain two iterators together.

fn main() {
    let height = 3;
    let width = 4;
    let horizontal = (0..height).map(|row| {let rw = row * width; rw..rw + width});
    horizontal.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
    let vertical = (0..width).map(|col| (0..height).map(move |n| col + n * width));
    vertical.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
    let all = horizontal.chain(vertical);
    //all.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
}

But the compiler complains about mismatched types.

error[E0271]: type mismatch resolving `<Map<std::ops::Range<{integer}>, [closure@src/main.rs:6:35: 6:82]> as IntoIterator>::Item == std::ops::Range<{integer}>`
 --> src/main.rs:8:26
  |
8 |     let all = horizontal.chain(vertical);
  |                          ^^^^^ expected struct `Map`, found struct `std::ops::Range`
  |
  = note: expected type `Map<std::ops::Range<{integer}>, [closure@src/main.rs:6:57: 6:81]>`
           found struct `std::ops::Range<{integer}>`

The signature of chain is:

fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter>ⓘ where
    U: IntoIterator<Item = Self::Item>

Both iterators have as Item type an Iterator with the same Item type, which admittedly is not quite what the signature demands. But I can call for example .for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");}) on each iterator, so why can't I construct the chain to call it on the chain? Is there another way to remove such code duplication?

5
  • 1
    for_each() is evil Commented Dec 5, 2021 at 14:42
  • @Stargateur Actually, after chain(), for_each() is preferred (more performant) - but the OP uses it before. Commented Dec 5, 2021 at 17:02
  • @ChayimFriedman I argue it's a bug (but yeah) that why I include the ultra bonus answer that don't use for_each but use collect (could be try_fold) that allow better perf and is actually a good use case. for_each is bad, collect is ok (in my eye) Commented Dec 5, 2021 at 17:03
  • 1
    @Stargateur I feel like using map() and collect() to replace for_each() is far more evil than just using for_each(). It's a nice trick, but for_each is there for a reason, and while I can immediately understand what a code with for_each() does (even if it's unpleasant to the eye), I'll have to take a second look on your version. Commented Dec 5, 2021 at 17:09
  • @ChayimFriedman, I would like to call chain+for_each once, but I am unable to call chain, and so forced to call for_each twice. Suppose I have many more sequences like horizontal and vertical... this is my question. Commented Dec 5, 2021 at 17:41

1 Answer 1

1

It's because your types doesn't have the same Item:

.map(|row| {let rw = row * width; rw..rw + width});
.map(|col| (0..height).map(move |n| col + n * width))

One is a Range the other is a Map of a Range.

The solution is to use flatten() or on your case flat_map():

fn main() {
    let height = 3;
    let width = 4;

    println!("Horizontal:");
    let horizontal = (0..height).flat_map(|row| {
        let rw = row * width;
        rw..rw + width
    });
    for x in horizontal.clone() {
        println!("{:?}", x);
    }

    println!("\nVertical:");
    let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
    for x in vertical.clone() {
        println!("{:?}", x);
    }

    println!("\nAll:");
    let all = horizontal.chain(vertical);
    for x in all {
        println!("{:?}", x);
    }
}

This made both vertical and horizontal iterator have the same Item type. Also, I remove for_each() in my opinion it's make the code unclear as for loop are for side effect that is imperative paradigm and iterator chaining is functional paradigm.


Bonus:

fn print_my_iter(name: &str, iter: impl Iterator<Item = i32>) {
    println!("{}:", name);
    for x in iter {
        println!("{:?}", x);
    }
}

fn main() {
    let height = 3;
    let width = 4;

    let horizontal = (0..height).flat_map(|row| {
        let rw = row * width;
        rw..rw + width
    });
    print_my_iter("Horizontal", horizontal.clone());

    let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
    print_my_iter("\nVertical", vertical.clone());

    let all = horizontal.chain(vertical);
    print_my_iter("\nAll", all);
}

Ultra bonus:

use std::io::{self, Write};

fn print_my_iter(name: &str, iter: impl Iterator<Item = i32>) -> Result<(), io::Error> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();

    writeln!(handle, "{}:", name)?;
    iter.map(|x| writeln!(handle, "{:?}", x)).collect()
}

fn main() -> Result<(), io::Error> {
    let height = 3;
    let width = 4;

    let horizontal = (0..height).flat_map(|row| {
        let rw = row * width;
        rw..rw + width
    });
    print_my_iter("Horizontal", horizontal.clone())?;

    let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
    print_my_iter("\nVertical", vertical.clone())?;

    let all = horizontal.chain(vertical);
    print_my_iter("\nAll", all)?;

    Ok(())
}
Sign up to request clarification or add additional context in comments.

1 Comment

IIUC If I use flatmap, then the sequence of sequences is flattened into a big sequence. For printing just the elements that makes little difference, but the subsequence are essential, so suppose I add parens in my print function (which I will edit in shortly).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.