Skip to content

← Closures and Iterators step 24 of 28

Hard Framework

Make your own collection iterable, all three ways

Item 9.5 taught you to consume iter, iter_mut and into_iter. Now build them.

pub struct Bag { pub items: Vec<i64> }

pub fn roundtrip(items: Vec<i64>) -> (Vec<i64>, i64, Vec<i64>)

Give Bag enough impls that roundtrip — which is written for you and must not change — compiles and runs:

let mut bag: Bag = items.iter().copied().collect();   // FromIterator

for x in &bag      { total += x; }                    // &Bag
for x in &mut bag  { *x *= 2; }                       // &mut Bag
bag.extend(items);                                    // Extend
for x in bag       { consumed.push(x); }              // Bag

It returns (doubled_contents, sum_of_the_originals, everything_consumed). For [1, 2, 3]: the sum is 6, the doubled contents are [2, 4, 6], and after extending with the originals the consumed contents are [2, 4, 6, 1, 2, 3].

The starter’s error is the whole lesson

It implements the obvious impl IntoIterator for Bag and stops. Then:

error[E0277]: `&Bag` is not an iterator
error[E0277]: `&mut Bag` is not an iterator

Read those types. &Bag is a different type from Bag, and it needs its own impl. There is no automatic “borrowed version” of your consuming impl, and no deref magic that produces one. That is exactly the fact item 9.6 established from the other side: Vec<T>, &Vec<T> and &mut Vec<T> are three types with three impls, which is why for x in &v yields references. std wrote all three by hand. So must you.

The three impls

impl IntoIterator for Bag {
    type Item = i64;
    type IntoIter = std::vec::IntoIter<i64>;
    fn into_iter(self) -> Self::IntoIter { self.items.into_iter() }
}

impl<'a> IntoIterator for &'a Bag {
    type Item = &'a i64;
    type IntoIter = std::slice::Iter<'a, i64>;
    fn into_iter(self) -> Self::IntoIter { self.items.iter() }
}

impl<'a> IntoIterator for &'a mut Bag {
    type Item = &'a mut i64;
    type IntoIter = std::slice::IterMut<'a, i64>;
    fn into_iter(self) -> Self::IntoIter { self.items.iter_mut() }
}

Three things worth pausing on.

You do not write a new iterator type. IntoIter is an associated type, and you are free to name someone else’s — here, the three iterators Vec and slices already provide. Delegating is not cheating; it is the normal way to make a wrapper type iterable, and it inherits their size_hint, DoubleEndedIterator and ExactSizeIterator impls for free.

The reference impls need a named lifetime. The 'a appears in the impl header, in Self (&'a Bag), and in both associated types. Try to elide it and you get E0106 (missing lifetime specifier); introduce it without using it in Self and you get E0207 (the type parameter is not constrained by the impl trait, self type, or predicates). The lifetime has to thread all the way through, and this is the smallest realistic example of why.

self in the reference impls is the reference. fn into_iter(self) on &'a Bag means self: &'a Bag, so self.items.iter() is exactly the right body — no extra borrow, no &self.

FromIterator and Extend

impl FromIterator<i64> for Bag { fn from_iter<T: IntoIterator<Item = i64>>(iter: T) -> Self }
impl Extend<i64> for Bag       { fn extend<T: IntoIterator<Item = i64>>(&mut self, iter: T) }

FromIterator is what makes collect() produce a Bag — the trait is the target of return-type-driven inference from item 9.21. Extend is what makes bag.extend(..) work, and is the “append to existing” counterpart from item 9.20. Both take IntoIterator, never Iterator, for the reason item 9.6 gave: it costs nothing and accepts strictly more callers.

Both bodies are one line, delegating to Vec.

The convention, and the two lints that police it

iter, iter_mut and into_iter are not language features. Nothing in the compiler knows those names. They are a naming convention std follows so consistently that it feels built in, and the convention is:

Inherent method Paired trait impl
fn iter(&self) impl IntoIterator for &Type
fn iter_mut(&mut self) impl IntoIterator for &mut Type
impl IntoIterator for Type

Clippy has two lints for the two halves, and this problem opts in to both with a crate-level attribute on the first line of the starter:

#![deny(clippy::into_iter_without_iter, clippy::iter_without_into_iter)]

Write the reference impls and forget the inherent methods, and you get:

error: `IntoIterator` implemented for a reference type without an `iter` method
help: consider implementing `iter`
error: `IntoIterator` implemented for a reference type without an `iter_mut` method
help: consider implementing `iter_mut`

Do it the other way round and iter_without_into_iter complains instead. Both are allow-by-default in normal projects, which is precisely why so many library types ship with one and not the other — and why callers of those types find that for x in &thing mysteriously does not work.

So Bag needs five impl items and two inherent methods. Each one is one line. The point is knowing which seven.

Grade is compile + tests + clippy -D warnings.

Loading visualization…