We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 23 of 28
Write your own adapters, with an extension trait
Two adapters of your own, reachable by method call on any iterator.
pub struct DedupAdjacent<I: Iterator> { /* .. */ }
pub struct Pairwise<I: Iterator> { /* .. */ }
pub trait IterExt: Iterator + Sized {
fn dedup_adjacent(self) -> DedupAdjacent<Self> { /* .. */ }
fn pairwise(self) -> Pairwise<Self> { /* .. */ }
}
impl<I: Iterator> IterExt for I {}
pub fn dedup_and_pair(nums: Vec<i64>) -> (Vec<i64>, Vec<(i64, i64)>)
-
dedup_adjacentdrops any item equal to the one just emitted.[1, 1, 2, 2, 2, 3]becomes1, 2, 3;[1, 2, 1]stays1, 2, 1— it is adjacent dedup, not global dedup. -
pairwiseyields every adjacent pair:[1, 2, 3]becomes(1, 2), (2, 3). It isslice::windows(2)at the iterator level, so an input ofnitems yieldsn - 1pairs, and fewer than two items yields nothing.
dedup_and_pair returns the deduplicated numbers and the pairs of the
deduplicated numbers.
Both adapters must implement size_hint correctly, and the tests check
it — including that Pairwise‘s hint is exact when its source is exact.
Why this is the most transferable pattern in the track
Every adapter in std is exactly this shape, and you have been reading their
names in error messages for the whole track:
Map<Filter<Iter<'_, i32>, {closure}>, {closure}>
That nest is not a wrapper hierarchy that costs a virtual call per level. Each
layer is a plain struct holding the layer below, each next is a small
generic function, and monomorphisation plus inlining collapses the whole tower
into one loop. Building your own tells you that from the inside.
The extension trait, and the two bounds you cannot skip
You cannot add a method to Iterator — it is not your trait. What you can do
is declare a new trait with default method bodies and blanket-implement it
for everything:
pub trait IterExt: Iterator + Sized { /* default bodies */ }
impl<I: Iterator> IterExt for I {}
The empty impl block is the whole implementation: every method already has a
body in the trait, so every iterator in the universe gets them for free. This
is how itertools works, and how you should ship any “I wish Iterator had
X” helper.
The starter omits the supertrait bounds, and both halves of the resulting error matter:
error[E0277]: `Self` is not an iterator
error[E0277]: the size for values of type `Self` cannot be known at compilation time
-
: Iterator— the default bodies storeselfin a struct whose impls requireI: Iterator. Without the supertrait,Selfis just some type. -
: Sized—Selfin a trait is?Sizedby default, andDedupAdjacent { inner: self, .. }has to putselfin a field by value, which needs a known size. Every extension trait takingselfby value needs this.
Where the Clone bound goes — the real design decision
Pairwise must yield each item twice: once as the right half of one pair
and once as the left half of the next. So it has to keep a copy, which means
Item: Clone. DedupAdjacent has to remember the last item it emitted to
compare against, so it needs Item: PartialEq + Clone too.
The question is where to write those bounds, and the answer is worth internalising:
// On the struct — infects every use, even ones that never call next().
pub struct Pairwise<I: Iterator> where I::Item: Clone { .. }
// On the impl — the bound is required only to iterate. Prefer this.
impl<I> Iterator for Pairwise<I> where I: Iterator, I::Item: Clone { .. }
Put a bound on the struct and it propagates to every function that so much as
names the type. Put it on the impl and it is demanded only where it is
actually needed. std follows the second rule almost everywhere; the struct
here carries I: Iterator only because the field type Option<I::Item>
mentions the associated type and cannot be written without it.
The hints
Get these right; they are checked.
-
DedupAdjacent: every remaining item could be a duplicate of the last one you emitted, so the lower bound collapses to0. The upper bound is unchanged — you never emit more than the source has left. So(0, inner_upper). This is exactly whyfilterreports(0, Some(n))in item 9.22’s table. -
Pairwise: pairs remaining is (items remaining, plus one if you are still holding the previous item) minus one — floored at zero.saturating_subis the right tool; a plain- 1onusizeunderflows on an empty source and, with-O, wraps to a colossal number instead of panicking.
type_complexity
Once adapters nest, clippy’s default-on type_complexity starts objecting to
the types you have to write down. Two fixes, both good: a type alias, or
returning impl Iterator<Item = T> from functions that build a chain — which
hides the tower entirely and lets you change it without touching callers.
A note on the future
Rust has gen blocks in the pipeline, which would let you write both of these
as a few lines of imperative code with yield. As of 1.95 they are
nightly-only, so on stable — and in this course — the struct is the way.
Knowing the struct form is not wasted: gen desugars to one.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.