Prettier way to transform iterator?

What is more idiomatic way to write this code? I'm trying to write some function that gets iterator with known Item type (but with generally unknown specific type) does some iterator actions (map, filter, itertools' group_by, etc) and returns transformed iterator. Return type of this function can be very complex and I have to write top-level functions to make it possible to specify the return type. I'm feeling it could be done in some more clear way.

#[derive(Clone)]
struct Coord {
    x: f64,
    y: f64,
}

fn get_x(c: Coord) -> f64 {
    c.x
}

fn coord_to_x<T: Iterator<Item = Coord>>(iter: T) -> std::iter::Map<T, fn(Coord) -> f64>{
    iter.map(get_x)
}

fn main() {
    let v1 = vec![Coord{x: 1.0, y: 2.0}; 5];
    let v2: Vec<_> = coord_to_x(v1.into_iter()).collect();
    println!("{:?}", v2);
}

Playground

This is what the impl Trait feature is for:

fn coord_to_x<T: Iterator<Item = Coord>>(iter: T) -> impl Iterator<Item = f64> {
    iter.map(|c| c.x)
}

Basically that syntax says that the function returns some concrete type that implements the trait Iterator, but it wont tell you which one.

Note that if you use IntoIterator instead as your generic bound, you can pass the vector directly.

1 Like

Thank you, Alice. Am I understand write that impl Trait object is not Sized and I cannot move it to another function?

No, it is not a trait object, that would be the dyn Trait syntax. The compiler does not use dynamic dispatch in this situation.

At compile time, the compiler will use type inference to figure out what the concrete actual type is, and use that as the return type. Note that this means that you have to actually return a specific concrete type. For example, this will fail to compile:

fn coord_to_x<T: Iterator<Item = Coord>>(iter: T) -> impl Iterator<Item = f64> {
    if some_boolean {
        iter.filter(|c| c.x > 0.5)
    } else {
        iter.map(|c| c.x)
    }
}

Whereas by boxing the iterator and using trait objects, you could return multiple different iterators depending on the situation.

3 Likes

Thank you!

1 Like

If you do come across the situation I gave as an example, you can use the Either type from itertools to merge the two kinds of iterators into a single type.

fn coord_to_x<T: Iterator<Item = Coord>>(iter: T) -> impl Iterator<Item = f64> {
    if some_boolean {
        Either::Left(iter.filter(|c| c.x > 0.5))
    } else {
        Either::Right(iter.map(|c| c.x))
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.