Why into_iter(self) and not into_iter(&self)

People are usually confused by into_iter(object) consuming the object, which is normally solved by calling into_iter(&object).
My question is why into_iter() has been designed for (self) and not (&self) in the first place ?
I am too new to rust to understand the involved scenario behind that choice.

into_iter(self) is meant to consume self and its elements, and used in cases where you do want to consume the elements. I think you are confusing this with iter(&self), which is a different method.

1 Like

Given an owned iterator (that is, one that gives up ownership of the elements of the sequence as it iterates), you can make a borrowed iterator fairly easily. Going the other direction - making an owned iterator from a borrowed iterator - is impossible in general, and the specific cases where it is possible usually require cloning the values. Rust tends away from implicit clones; if you want that, you have to ask for it explicitly.

The pattern of foo.iter() borrowing from foo and foo.into_iter() moving foo and its items outright is a fairly natural, and once you get used to it, fairly ergonomic way to address both sets of needs.

Also, you can have impl IntoIterator for &Collection -- then its self is a reference.

1 Like

If it was designed with &self you wouldn't be able to consume the object and getting owned items while iterating. Instead, by taking self, it allows you to do that but also still retain the ability to iterate over reference by just implementing IntoIterator for references to your collection.

My understanding was that into_iter() was a friendly helper to let the compiler infer .iter() or .iter_mut() from the context.
Your answer tells another story where I am totally wrong, and into_iter() was designed to transform self, and thus naturally consuming it.

The convention for a collection implements three types of iterator, with corresponding implementations of IntoIterator. Note how into_iter is a method on that trait, and always takes the implementor by value. (It can still be a reference, when the implementor is a reference.)

// Owning iterator
impl<T> IntoIterator for Collection<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;
    fn into_iter(self) -> Self::IntoIter { todo!() }
}

// Shared borrow iterator
impl<'a, T> IntoIterator for &'a Collection<T> {
    type Item = &'a T;
    type IntoIter = Iter<'a, T>;
    fn into_iter(self) -> Self::IntoIter { todo!() }
}

// Exclusive borrow iterator
impl<'a, T> IntoIterator for &'a mut Collection<T> {
    type Item = &'a mut T;
    type IntoIter = IterMut<'a, T>;
    fn into_iter(self) -> Self::IntoIter { todo!() }
}

Then collection.into_iter() will just call the trait method, whereas .iter() and .iter_mut() are by convention inherent methods on the Collection that provide a way to ergonomically produce borrowing iterators.

impl<T> Collection<T> {
    fn iter(&self) -> Iter<'_, T> {
        <&Self as IntoIterator>::into_iter(self)
    }
    fn iter_mut(&mut self) -> IterMut<'_, T> {
        <&mut Self as IntoIterator>::into_iter(self)
    }
}

In combination with how for loops work, that means the loops in each of these examples act the same.

fn example_owned(coll: Collection<String>) {
    // n.b. you can't use both loops in one function since it consumes
    // the collection
    for string in coll.into_iter() { .. }
    for string in coll { .. }
}
fn example_shared(coll: Collection<String>) {
    for ref_string in coll.iter() { .. }
    for ref_string in &coll { .. }
}
fn example_excl(mut coll: Collection<String>) {
    for ref_mut_string in coll.iter_mut() { .. }
    for ref_mut_string in &mut coll { .. }
}

The method versions are more ergonomic when you need to chain some adapters together, for example.

fn example(coll: Collection<String>) {
    for _ in coll.iter().filter(|s| s.len() > 0) { .. }

    // versus
    for _ in (&coll).into_iter().filter(|s| s.len() > 0) { .. }
}
4 Likes

Note that the into preposition in general means "move and consume" in Rust. You're turning A into B, consuming A in the process.

1 Like

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.