Trait for `iter` and `iter_mut`

I've found myself in a situation where I want to ask the caller of a function for a type of some sort that implements IntoIterator<Item = T>, but where I can also borrow the object beforehand and iterate over references to its contents. In other words, an object that has both an into_iter() and an iter() method. The reason is that I need to check the contents of the object first and perhaps error out, and if the contents is satisfactory, call another function that wants IntoIterator<Item = T>.

Unfortunately there is no trait associated with the iter method, so this doesn't seem possible. Has it ever been considered to make a trait for iter (and iter_mut)?

1 Like

Could you give the function (handwaving the signature for that parameter) and its semantics?

1 Like

Typically, you could use something like where I: IntoIterator<Item = T>, for<'a> &'a I: IntoIterator<Item = &'a T> for this. If that's too lengthy, you could write your own trait to abbreviate the where clause. If you want to support types I whose .iter() method returns something other than &<I as IntoIterator>::Item - like e. g. HashMap - then it isn't clear what kind of connection between the I::Item and <&I>::Item types you need for your function.

5 Likes

That’s a bit non-trivial without GATs, but here’s one possible approach:

trait HasIter<'a, _Dummy = &'a Self, _Item = &'a <Self as IntoIterator>::Item>: IntoIterator {
    type Iter: Iterator<Item = _Item>;
}

impl<'a, I> HasIter<'a> for I
where
    I: IntoIterator,
    &'a I: IntoIterator<Item = &'a Self::Item>,
{
    type Iter = <&'a I as IntoIterator>::IntoIter;
}

type Iter<'a, I> = <I as HasIter<'a>>::Iter;

trait Container: IntoIterator + for<'a> HasIter<'a> {
    fn iter(&self) -> Iter<'_, Self>;
}

impl<T> Container for T
where
    T: IntoIterator,
    for<'a> &'a T: IntoIterator<Item = &'a T::Item>,
{
    fn iter(&self) -> Iter<'_, Self> {
        self.into_iter()
    }
}

fn _demonstration(x: impl Container<Item = u8>) {
    for i in x.iter() {
        let _: &u8 = i;
    }

    for i in x.into_iter() {
        let _: u8 = i;
    }
}

fn _test(x: Vec<u8>) {
    _demonstration(x);
}

(playground)

Note that that’s not exactly just an abbreviation of that where clause, because it isn’t really (currently) possible to make a trait bound T: Foo imply a trait bound &T: Bar that’s on &T instead of T itself. This means with a T: Container type as above you cannot write for ... in &x anymore on a x: T. On the other hand, the trait above manages to offer an iter() method, which is something you don’t get with &T: IntoIterator<Item = &...>; in the latter case you’d have to do something like (&x).into_iter().

2 Likes

I have occasionally pondered adding something like this to IntoIterator:

As that would be ignored in favour of the inherent iter and iter_muts, but would mean that new code could just add the trait implementations (which they should be doing regardless) and not have to add the inherent forwarders.

Dunno whether it's actually worth doing, though.

3 Likes

Along those lines, in rayon we have IntoParallelRefIterator and IntoParallelRefMutIterator and blanket impls with &Self / &mut Self constraints. In part, we needed traits because we can't add inherent par_iter and par_iter_mut to existing types, so instead these are in rayon::prelude.

I'm not sure of the value of adding the constrained methods to IntoIterator. Concrete types will already get the inherent method as you say, but in generic context, I: IntoIterator won't give you those methods since you'll also need to match the extra constraints. I suppose if the generic code needs both value and ref iterators, then having the distinct names will make it easier to call.

1 Like

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