Skip to content

← Closures and Iterators step 6 of 28

Medium Primitives

IntoIterator: one bound that accepts five containers

Write one generic summing function, then feed it five different containers without changing it.

pub fn sum_all<I>(src: I) -> i64
where
    I: IntoIterator<Item = i64>;

pub fn dispatch(kind: String, data: Vec<i64>) -> i64

dispatch builds a container from data according to kind and hands it to sum_all:

kind what to pass
"vec" the Vec<i64> itself
"slice" data.iter().copied() — an iterator, not a container
"array" a [i64; 4] holding the first four entries, zero-padded
"deque" a VecDeque<i64> built from data
"set" a BTreeSet<i64> built from data

Anything else returns -1.

Two of those rows are deliberately not sum-preserving, and the hidden cases check both: an array has a fixed length, so entries past the fourth are dropped; and a BTreeSet deduplicates, so [5, 5, 5] sums to 5. For input with at most four entries and no duplicates, all five kinds agree — which is the point of the exercise.

The starter compiles nothing, and its error is the lesson

The starter declares the bound as I: Iterator<Item = i64>, which is the bound almost everyone writes first. Compile it:

error[E0277]: `Vec<i64>` is not an iterator

Correct. A Vec is not an iterator. It has never been one. Neither is an array, a VecDeque, a BTreeSet, a HashMap, a String, or a Range — wait, a Range actually is one, and we will come back to that.

A Vec is a thing that can produce an iterator. That capability is a separate trait:

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;
    fn into_iter(self) -> Self::IntoIter;
}

Change the bound to IntoIterator<Item = i64>, call .into_iter() on src inside the body, and all five arms compile against the same signature.

How for actually works

There is no for loop in Rust’s semantics. There is a desugaring:

for x in expr {
    body
}

becomes, near enough,

let mut it = IntoIterator::into_iter(expr);
while let Some(x) = it.next() {
    body
}

That single line explains the three forms you have been memorising. std provides three separate impls for Vec<T>, and the one that gets picked depends only on what you wrote after in:

You write Impl selected Item
for x in v impl IntoIterator for Vec<T> T — the values, v is consumed
for x in &v impl IntoIterator for &Vec<T> &T
for x in &mut v impl IntoIterator for &mut Vec<T> &mut T

So for x in &v gives you references not because for is being helpful, but because &Vec<T> is a different type with a different impl whose associated Item is &T. And v.iter() is simply a shorter way to name the second one — the standard library’s iter() is defined as calling (&self).into_iter(). Clippy has a default-on lint, into_iter_on_ref, that tells you so when you write it the long way.

There is also a blanket impl impl<I: Iterator> IntoIterator for I, whose into_iter just returns self. Every iterator is trivially into-iterable.

That blanket impl is why I: IntoIterator is strictly more permissive than I: Iterator — it accepts every iterator plus every container. There is no cost and no downside. This is why essentially every generic function in std that consumes a sequence — Extend::extend, FromIterator, Iterator::chain, Iterator::zip — takes IntoIterator, not Iterator. Copy the habit. Accepting Iterator when you meant IntoIterator forces every caller to write .iter() for no reason, and forbids the ones holding a Vec from passing it at all.

The array row is not filler

[i64; 4] is a distinct type from [i64; 5]the length is part of the type. That is why you cannot build one from a runtime-length Vec without deciding what to do about the mismatch, and why this problem specifies zero-padding and truncation rather than leaving it to you.

Arrays gained a by-value IntoIterator impl in Rust 1.53, and edition 2021 changed array.into_iter() in method position to yield values rather than references. In edition 2024 — which is what compiles here — for x in arr gives you i64, not &i64. If you find a tutorial claiming otherwise, it predates that change.

Building the containers

VecDeque has a From<Vec<T>> impl that reuses the allocation. BTreeSet does not; you collect into it, which needs a turbofish or a typed binding so inference knows the target — item 9.21 is about exactly that. And .iter().copied() turns &i64 into i64, which is item 9.7.

Grade is compile + tests + clippy -D warnings.

Loading visualization…