'for x in &blah' vs 'for x in blah.iter()'

(I just read earlier today that the following are supposedly equivalent.)

Is there any advantage of:

'for x in blah.iter()' over 'for x in &blah' or
'for x in blah.mut_iter()' over 'for x in &mut blah'

?

There is normally no difference other than &blah requiring less characters than blah.iter().

For example, the IntoIterator implementation for &[T] and &mut [T] literally call iter() and iter_mut().

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T> IntoIterator for &'a [T] {
    type Item = &'a T;
    type IntoIter = Iter<'a, T>;

    fn into_iter(self) -> Iter<'a, T> {
        self.iter()
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T> IntoIterator for &'a mut [T] {
    type Item = &'a mut T;
    type IntoIter = IterMut<'a, T>;

    fn into_iter(self) -> IterMut<'a, T> {
        self.iter_mut()
    }
}

(github)

6 Likes

To be pedantic,

for _ in _

always calls IntoIterator. But it just so happens that the impls for &[T], &mut [T] call .iter() and .mut_iter() ?

Yes, that's correct. The for-loop will desugar into a <&[T] as IntoIterator>::into_iter(...) call, which internally just calls <[T]>.iter().

Note that this is only a convention that pretty much all standard library types with iter()-like methods follow. There is nothing stopping me from giving my type an iter() method then doing something completely different in the impl IntoIterator for &MyType impl.

6 Likes

Nitpick: iter is a method on [T] that takes &self, not a method on &[T]. You can call it on a value of type &[T] because of the implicit dereferencing that takes place during method resolution.

2 Likes

Note that there's no reason why it couldn't be written the other way around: [T]::iter could instead call <&[T]>::into_iter. Or they could both be independently implemented to do the same thing without calling each other at all. That into_iter calls iter and not the other way around is no more than an implementation detail.

I prefer to think of a.iter() as shorthand for (&a).into_iter() because IntoIterator is more fundamental, in a way, than the convention of naming a by-reference iteration method iter.

8 Likes

It'd be nice if we could create some sort of Iter trait which turns this convention into a pattern that can be relied on (e.g. implement IntoIterator for references and get a iter() method for free).

It seems like this requires GATs though, and when I tried it the closest I could get to was this generics soup:

#![feature(generic_associated_types)]

trait Iter {
    type Iterator<'a>: Iterator + 'a;
    type Item<'a>: 'a;

    fn iter(&self) -> Self::Iterator<'_>;
}

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

    fn iter(&self) -> Self::Iterator<'_> {
        IntoIterator::into_iter(self)
    }
}

(playground)

Which triggered some really weird compiler errors that I eventually created a ticket for.

2 Likes

You can make this work if you parameterize Iter with a lifetime:


trait Iter<'a> {
    type Iterator: Iterator<Item=&'a Self::Item> + 'a;
    type Item: 'a;

    fn iter(&'a self) -> Self::Iterator;
}

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

    fn iter(&'a self) -> Self::Iterator {
        IntoIterator::into_iter(self)
    }
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=636c255503a9443d4ce2b1682cbb8682

3 Likes

Or you can do something like this to capture the full convention:

trait Iterable: IntoIterator<Item = <Self as Iterable>::Item>
where
    for<'a> &'a Self: IntoIterator<Item = &'a <Self as Iterable>::Item>,
    for<'a> &'a mut Self: IntoIterator<Item = &'a mut <Self as Iterable>::Item>,
{
    type Item;
    fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
        self.into_iter()
    }

    fn iter_mut(&mut self) -> <&'_ mut Self as IntoIterator>::IntoIter {
        self.into_iter()
    }
}

impl<T, I> Iterable for T
where
    T: IntoIterator<Item = I>,
    for<'a> &'a T: IntoIterator<Item = &'a I>,
    for<'a> &'a mut T: IntoIterator<Item = &'a mut I>,
{
    type Item = I;
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=5285f390232314254983ba8a19c6517c

8 Likes

I'd love something like this, though I wonder if it would be better to define a trait providing iter and a separate trait providing iter_mut, assuming there's cases where a data type (that doesn't have into_iter) would just want iter without iter_mut.

(Not intended as a criticism of your example, which demonstrates the concept efficiently)

1 Like

If I were to build this out more, Iā€™d probably make it a stack of traits similar to Fn{,Once,Mut}. So, IterMut implies Iter which in turn implies IntoIterator, but immutable containers can have Iter without IterMut.

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.