Search code examples
rustiterator

Rust unpack iter of structs to iter of fields


Assume I have a struct like this

struct Foo {
    a: i32,
    b: f64,
    c: String,
    d: u8,
}

And bar is of type Vec<Foo>, which already contains many elements.

I would like to have three iterators of Foo's first three fields (a, b and c) respectively. I know how to use unzip to achieve unpacking bar to its fields .

let (vec_a, (vec_b, vec_c)): (Vec<i32>, (Vec<f64>, Vec<String>)) = bar
    .iter()
    .map(|elem| (elem.a, (elem.b, elem.c)))
    .unzip();

But I don't know how to make iterators in ways alike.

let (iter_a, (iter_b, iter_c)): (
    Box<dyn Iterator<Item = i32>>,
    (Box<dyn Iterator<Item = f64>>, Box<dyn Iterator<Item = String>>),
) = bar.iter().map(|elem| (elem.a, (elem.b, elem.c)));

This results in error[E0308]: mismatched types. Did I miss something or I just cannot use the syntax above at all.

Also as another question, unzip can only unzip pairs, if the struct has many fields, the nested level will soon explode, is there any better way to achieve my goal?


Solution

  • I would simply make three distinct iterators.

    In the example below, the first part uses .unzip() as you did but fixes some errors. .iter() prevents from consuming elem.c. If we do not want to use .into_iter(), we need either a reference (.as_str()) or a clone (.clone()).

    The second part simply makes three distinct iterators. It's the solution I prefer, as stated in the first sentence.

    The third part tries to keep your original idea: start from one iterator and expect to split it in three. It could make sense if the process of obtaining the initial iterator was quite complicated and we didn't want to repeat it three times. In this case, we have to clone the initial iterator itself and add a final .map() stage to each one.

    struct Foo {
        a: i32,
        b: f64,
        c: String,
        _d: u8,
    }
    
    fn show_iter<I, E>(
        title: &str,
        iter: I,
    ) where
        I: Iterator<Item = E>,
        E: std::fmt::Debug,
    {
        print!("{}:", title);
        for e in iter {
            print!(" {:?}", e);
        }
        println!();
    }
    
    fn main() {
        let bar = vec![
            Foo {
                a: 1,
                b: 2.3,
                c: "X".to_owned(),
                _d: 4,
            },
            Foo {
                a: 2,
                b: 3.4,
                c: "Y".to_owned(),
                _d: 5,
            },
            Foo {
                a: 3,
                b: 4.5,
                c: "Z".to_owned(),
                _d: 6,
            },
        ];
        {
            // or use   vec_c: Vec<String>   and   elem.c.clone()
            let (vec_a, (vec_b, vec_c)): (Vec<i32>, (Vec<f64>, Vec<&str>)) = bar
                .iter()
                .map(|elem| (elem.a, (elem.b, elem.c.as_str())))
                .unzip();
            println!("vec_a: {:?}", vec_a);
            println!("vec_b: {:?}", vec_b);
            println!("vec_c: {:?}", vec_c);
        }
        println!("~~~~~~~~~~~~~~~~");
        {
            let iter_a = bar.iter().map(|elem| elem.a);
            let iter_b = bar.iter().map(|elem| elem.b);
            // or use   elem.c.clone()
            let iter_c = bar.iter().map(|elem| elem.c.as_str());
            show_iter("iter_a", iter_a);
            show_iter("iter_b", iter_b);
            show_iter("iter_c", iter_c);
        }
        println!("~~~~~~~~~~~~~~~~");
        {
            // or use   elem.c.clone()
            let iter_abc =
                bar.iter().map(|elem| (elem.a, elem.b, elem.c.as_str()));
            let iter_a = iter_abc.clone().map(|(a, _b, _c)| a);
            let iter_b = iter_abc.clone().map(|(_a, b, _c)| b);
            let iter_c = iter_abc.map(|(_a, _b, c)| c);
            show_iter("iter_abc iter_a", iter_a);
            show_iter("iter_abc iter_b", iter_b);
            show_iter("iter_abc iter_c", iter_c);
        }
    }
    /*
    vec_a: [1, 2, 3]
    vec_b: [2.3, 3.4, 4.5]
    vec_c: ["X", "Y", "Z"]
    ~~~~~~~~~~~~~~~~
    iter_a: 1 2 3
    iter_b: 2.3 3.4 4.5
    iter_c: "X" "Y" "Z"
    ~~~~~~~~~~~~~~~~
    iter_abc iter_a: 1 2 3
    iter_abc iter_b: 2.3 3.4 4.5
    iter_abc iter_c: "X" "Y" "Z"
    */