Skip to content

← Closures and Iterators step 24 of 28

Hard Framework

Make your own collection iterable, all three ways

Add borrowed iteration to Bag, a wrapper around Vec<i64>. Keep the supplied roundtrip function and the two enabled Clippy lints unchanged.

The starter already implements FromIterator<i64>, Extend<i64> and consuming IntoIterator for Bag. Add these four pieces:

  • IntoIterator for &Bag, yielding shared references.
  • IntoIterator for &mut Bag, yielding mutable references.
  • An inherent iter(&self) method.
  • An inherent iter_mut(&mut self) method.

Delegate to the inner vector and its slice iterators. You do not need a custom iterator type or a copy of the vector for borrowed iteration.

What roundtrip does

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

It collects the input values into a Bag, sums them through shared iteration, doubles them through mutable iteration, and copies the doubled values into a snapshot. It then appends the original input and consumes the bag.

The return order is (doubled, original_sum, consumed). For [1, 2, 3], that is ([2, 4, 6], 6, [2, 4, 6, 1, 2, 3]). Empty input produces ([], 0, []). Preserve element order and duplicates. Test inputs keep doubling and the running sum within i64 range.

Each loop uses a different receiver type

Loop Trait implementation Yielded item Effect on the bag
for x in &bag IntoIterator for &Bag &i64 Borrows its contents for reading
for x in &mut bag IntoIterator for &mut Bag &mut i64 Borrows its contents for mutation
for x in bag IntoIterator for Bag i64 Consumes the bag

A for loop uses IntoIterator to obtain an iterator. Implementing the trait for Bag does not generate implementations for either reference type. That is why the starter fails at the borrowed loops even though consuming iteration is already implemented.

Connect the borrowed items to their lifetime

The shared implementation uses these associated types:

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()
    }
}

Here, Self is &'a Bag. The self parameter already is a reference; changing the receiver to &self would give the wrong trait signature. The named lifetime connects that borrow to both the iterator and the references it yields.

For mutable iteration, use &'a mut Bag, &'a mut i64 and std::slice::IterMut<'a, i64>, delegating to self.items.iter_mut(). The borrow checker prevents conflicting access while the mutable borrow is still in use. Consuming iteration already returns std::vec::IntoIter<i64>.

Naming these existing iterator types also preserves their capabilities, such as exact remaining length and iteration from the back. Bag itself does not need to implement Iterator.

Provide the matching methods

Add these inherent method signatures and delegate their bodies to the vector:

pub fn iter(&self) -> std::slice::Iter<'_, i64>
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, i64>

The placeholder lifetime '_ works in these method return types because it is inferred from the receiver borrow. In the trait implementations above, name the lifetime so the associated types can refer to it.

iter and iter_mut are conventional method names. Providing those methods alone does not make the borrowed for loops work; the trait implementations are also needed. This exercise enables both directions of Clippy's convention checks:

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

Retain that attribute. The solution must compile, return the expected values, and pass Clippy with warnings treated as errors.

Collection building is already implemented

FromIterator<i64> lets collect() produce a Bag; Extend<i64> appends values to an existing one. Both methods accept an IntoIterator, so callers can supply a collection or an iterator. Their existing implementations simply delegate to Vec and need no changes for this task.