IntoIterator over owned or borrows values

I have a custom type called Streamlines. It's a list of lines in 3D, implemented as a vector of offsets and a vector of points to avoid allocating too much. Iterating on a Streamlines returns slices of points. It returns slices because all the points are in a single vector and I do not want to copy uselessly.

I created my own Reader to read the streamlines files written on disk. It reads the file streamline per streamline. Iterating on a Reader returns a vector of points. It returns owned data because the data doesn't exist anywhere. It's created while reading.

I tried to write a function that accept both kind of data

fn wathever<I>(streamlines: I)
    I: IntoIterator<Item = &'a [Point]> // Works only for the Streamlines
    OR
    I: IntoIterator<Item = Vec<Point>, // Works only for the Reader
{ 
    for streamline in &streamlines {
        // Do something with the points (no modification)
    }
}

I understand why both versions work only on a single type. This is normal and simple enough. What I don't understand is how to make it work for both types. I tried using AsRef and Borrow but I don't think it's supposed to be used in an iterator. Is it possible to write such a function in Rust?

You could bound the iterator's item type with AsRef<[Point]> or something like that:

fn wathever<I>(streamlines: I)
where
    I: IntoIterator,
    I::Item: AsRef<[Point]>
{
    // ...
}
2 Likes

_: AsRef<[T]> seems like a decent choice for unifying Vec<T> and &[T].

1 Like

Right, I tested this

I: IntoIterator<Item = AsRef<[Point]>>,

but it doesn't work. It says "The size for value of type XYZ cannot be known at compile time". I'm not sure why it's not equivalent...

Your version on 2 lines works perfectly. Thanks you @SkiFire13

You're using a trait AsRef<[Point]> in a place where a type is expected. In principle this doesn't work at all; in practice, AsRef<[Point]> is also a (deprecated) way of writing dyn AsRef<[Point]>; which is a type .. more accurately the type of trait objects for the AsRef<[Point]> trait. Trait objects are unsized and iterator items are sized which is why you're getting the error message.

The right place for a trait to go it on the right-hand-side of a trait bound, i.e. a bound Type: Trait where it constrains a type. That type is the type I::Item. If it was a thing, perhaps something like IntoIterator<Item: AsRef<[Point]>> would be a reasonable notational equivalent, too, but rust doesn't support such syntax, so it needs to go on a separate line. One (mostly) equivalent alternative is to write IntoIterator<Item = T> where T is a new type variable, and put a trait bound T: AsRef<[Point]> separately.

fn wathever<I, T>(streamlines: I)
where
    I: IntoIterator<Item = T>,
    T: AsRef<[Point]>
{
    // ...
}

But the additional type argument is rather useless in this case, so the version that SkiFire13 presented above is a bit more clean.

3 Likes

It's actually supported with the unstable associated_type_bounds feature.

2 Likes

You should upgrade to the current 2021 edition https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html#rust-2021.

One of the few changes in the edition is to remove this deprecated shorthand, so you get a better error: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3fcd88e99162d7de102af2778630d5fe

error[E0782]: trait objects must include the `dyn` keyword
 --> src/lib.rs:4:34
  |
4 |     where I: IntoIterator<Item = AsRef<[Point]>>
  |                                  ^^^^^^^^^^^^^^

Now, that's still imperfect for this case since you don't want dyn AsRef there, but at least it starts talking about dyn so you're more likely to notice what's going on.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.