Search code examples
rustiteratorchain

Unable to use chain method inside of for loop


as I understand it, Calling the chain method on an iterator consumes it and returns a new type. This is a problem, because I need to chain multiple iterators together inside a for loop. My first attempt:

pub fn convert(s: String, num_rows: i32) -> String {
    let mut iter = s.chars().enumerate().filter_map(|(i, c)| {
        if i % ((num_rows as usize - 1)*2) == 0 {Some(c)} else {None}});
    for n in 1..num_rows as usize {
        iter = iter.chain(
            s.chars().enumerate().filter_map(|(i,c)| {
                let cycle = (num_rows as usize - 1)*2;
                if i % cycle == n || i % cycle == cycle - n {Some(c)} else {None}
            })
        );
    }
    iter.collect()
}

This gives me an error:

error[E0308]: mismatched types
  --> src/lib.rs:5:16
   |
2  |       let mut iter = s.chars().enumerate().filter_map(|(i, c)| {
   |  _____________________________________________________-
3  | |         if i % ((num_rows as usize - 1)*2) == 0 {Some(c)} else {None}});
   | |                                                                      -
   | |                                                                      |
   | |______________________________________________________________________the expected closure
   |                                                                        the found closure
4  |       for n in 0..num_rows as usize {
5  |           iter = iter.chain(
   |  ________________^
6  | |             s.chars().enumerate().filter_map(|(i,c)| {
7  | |                 let cycle = (num_rows as usize - 1)*2;
8  | |                 if i % cycle == n || i % cycle == cycle - n {Some(c)} else {None}
9  | |             })
10 | |         );
   | |_________^ expected struct `FilterMap`, found struct `std::iter::Chain`
   |
   = note: expected struct `FilterMap<Enumerate<Chars<'_>>, _>`
              found struct `std::iter::Chain<FilterMap<Enumerate<Chars<'_>>, _>, FilterMap<Enumerate<Chars<'_>>, [closure@src/lib.rs:6:46: 9:14]>>`

no big deal, I thought. I'll just let it infer the type inside the loop, like this:

pub fn convert(s: String, num_rows: i32) -> String {
    let mut iter;
    for n in 0..num_rows as usize {
        iter = iter.chain(
            s.chars().enumerate().filter_map(|(i,c)| {
                let cycle = (num_rows as usize - 1)*2;
                if i % cycle == n || i % cycle == cycle - n {Some(c)} else {None}
            })
        );
    }
    iter.collect()
}

This gives me a new error saying I need to declare a type:

error[E0282]: type annotations needed
 --> src/lib.rs:4:16
  |
2 |     let mut iter;
  |         -------- consider giving `iter` a type
3 |     for n in 0..num_rows as usize {
4 |         iter = iter.chain(
  |                ^^^^ cannot infer type
  |
  = note: type must be known at this point

So, after that I tried adding in the type annotations from the earlier compiler error:

use core::str::Chars;
use core::iter::*;
let mut iter: Chain<FilterMap<Enumerate<Chars<'_>>, _>, FilterMap<Enumerate<Chars<'_>>, _>>;

This looks quite ugly, and it still didn't work:

error[E0308]: mismatched types
  --> src/lib.rs:6:16
   |
6  |           iter = iter.chain(
   |  ________________^
7  | |             s.chars().enumerate().filter_map(|(i,c)| {
8  | |                 let cycle = (num_rows as usize - 1)*2;
9  | |                 if i % cycle == n || i % cycle == cycle - n {Some(c)} else {None}
10 | |             })
11 | |         );
   | |_________^ expected struct `std::iter::FilterMap`, found struct `std::iter::Chain`
   |
   = note: expected struct `std::iter::Chain<std::iter::FilterMap<std::iter::Enumerate<Chars<'_>>, _>, std::iter::FilterMap<std::iter::Enumerate<Chars<'_>>, _>>`
              found struct `std::iter::Chain<std::iter::Chain<std::iter::FilterMap<std::iter::Enumerate<Chars<'_>>, _>, std::iter::FilterMap<std::iter::Enumerate<Chars<'_>>, _>>, std::iter::FilterMap<std::iter::Enumerate<Chars<'_>>, [closure@src/lib.rs:7:46: 10:14]>>`

So, what am I missing? How can this work?


Solution

  • You can't do that directly because you would need a different type at each loop iteration: Chain<FilterMap, FilterMap> the first time, then Chain<Chain<FilterMap, FilterMap>, FilterMap> the second time, then Chain<Chain<Chain<FilterMap, FilterMap>, FilterMap>, FilterMap> the third time, and so on.

    This issue can be solved by boxing the iterators:

    pub fn convert(s: String, num_rows: i32) -> String {
        let mut iter: Box<dyn Iterator<Item=char>> = Box::new (s.chars().enumerate().filter_map(|(i, c)| {
            if i % ((num_rows as usize - 1)*2) == 0 {Some(c)} else {None}}));
        for n in 1..num_rows as usize {
            iter = Box::new(iter.chain(
                s.chars().enumerate().filter_map(|(i,c)| {
                    let cycle = (num_rows as usize - 1)*2;
                    if i % cycle == n || i % cycle == cycle - n {Some(c)} else {None}
                }))
            );
        }
        iter.collect()
    }
    

    Playground

    Which gives you a lifetime error about n being borrowed by the closure, easily fixed by adding move:

    pub fn convert(s: String, num_rows: i32) -> String {
        let mut iter: Box<dyn Iterator<Item=char>> = Box::new (s.chars().enumerate().filter_map(|(i, c)| {
            if i % ((num_rows as usize - 1)*2) == 0 {Some(c)} else {None}}));
        for n in 1..num_rows as usize {
            iter = Box::new(iter.chain(
                s.chars().enumerate().filter_map(move |(i,c)| {
                    let cycle = (num_rows as usize - 1)*2;
                    if i % cycle == n || i % cycle == cycle - n {Some(c)} else {None}
                }))
            );
        }
        iter.collect()
    }
    

    Playground