Search code examples
rustiteratorclosures

In nested iterators accessing vector by index does not work


I have a strange behavior that I cannot explain to myself. If want to create new Strings by iterating over two Vec<String>'s, but somehow access by index doesn't work, because I borrow stuff. My confusion comes from the fact that the index should not be copied as it is a primitive time.

The example does work if I iterate directly over the Vec<String>'s. The compiler error's suggestion of using move in the second map does not fix the problem.

Here is my example:

fn f() -> Vec<String> {
    let num_outer = 2;
    let num_inner = 3;

    let outer_names = vec!["1", "2"];
    let inner_names = vec!["x", "y", "z"];

    (0..num_outer)
        .into_iter()
        .map(|outer_index| {
            (0..num_inner).into_iter().map(|inner_index| {
                format!(
                    "tuple ({},{})",
                    outer_names[outer_index], inner_names[inner_index],
                )
            })
        })
        .flatten()
        .collect::<Vec<String>>()
}

fn main() {
    let x = f();

    println!("{:?}", &x);
}

Solution

  • This is because you need your closures to capture the indices by move (or by Copy, since they're Copy types), but also capture the Vecs by reference. Rust only lets you choose one type of capture syntactically (it's by move if you make a move closure, otherwise it captures everything by reference).

    However, it's easy to do it manually. You just use a move closure and "move-in" references that you create before the closure. In your case, this works:

    fn f() -> Vec<String> {
        let num_outer = 2;
        let num_inner = 3;
    
        let outer_names = vec!["1", "2"];
        let inner_names = vec!["x", "y", "z"];
    
        let outer_names_ref = &outer_names;
        let inner_names_ref = &inner_names;
        (0..num_outer)
            .flat_map(|outer_index| {
                (0..num_inner).map(move |inner_index| {
                    format!(
                        "tuple ({},{})",
                        outer_names_ref[outer_index], inner_names_ref[inner_index],
                    )
                })
            })
            .collect::<Vec<String>>()
    }
    
    fn main() {
        let x = f();
    
        println!("{:?}", &x);
    }
    

    Note I also removed the unnecessary into_iters, since the range literals are already iterators. I also switched to flat_map because clippy suggested it (use clippy! it's great :) )


    In general, if you need "mixed captures" for a closure (i.e., move captures for some variables, reference captures for others), this is the way to do it. You use a move closure, but you explicitly create the references that you need to move in to capture things "by reference".