We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 24 of 28
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…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.