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.
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.
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) { .. }
}
Note that the into
preposition in general means "move and consume" in Rust. You're turning A into B, consuming A in the process.
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.