We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 7 of 28
The double-reference wall: &&x, copied and cloned
Filter two borrowed slices and return owned results.
pub fn evens_then_names(nums: &[i64], names: &[String]) -> (Vec<i64>, Vec<String>)
-
.0— every even number fromnums, in order. -
.1— every name whose byte length is at least 4, in order.
For nums = [1, 2, 3, 4] and names = ["ab", "abcd", "xyz"] the answer is
([2, 4], ["abcd"]).
Both parameters are shared slices. You are borrowing. The caller still owns everything and uses it after you return, so you may not move a single element out — the outputs have to be copies and clones. That constraint is the whole exercise.
The starter is the code you would have written
let evens: Vec<i64> = nums.iter().filter(|n| n % 2 == 0).collect();
let long_names: Vec<String> = names.iter().filter(|s| s.len() >= 4).collect();
Nothing about that looks wrong, and it produces three errors:
error[E0369]: cannot calculate the remainder of `&&i64` divided by `{integer}`
error[E0277]: a value of type `Vec<i64>` cannot be built from an iterator
over elements of type `&i64`
error[E0277]: a value of type `Vec<String>` cannot be built from an iterator
over elements of type `&String`
&&i64. Two ampersands. Where did the second one come from?
Where the second & comes from
Two facts, and they compose:
-
slice.iter()yields&T, notT. It is a borrowing iterator; that is its entire job. Sonums.iter()hasItem = &i64. -
Iterator::filteris declared asfn filter<P>(self, predicate: P) -> Filter<Self, P> where P: FnMut(&Self::Item) -> bool;Look at the
&.filterhands its predicate a reference to the item, because filtering must not consume what it is inspecting — an item that passes has to survive to be yielded.
Compose them: Item = &i64, so &Self::Item = &&i64. Your closure parameter
is a double reference. This is not a quirk; it is the only signature filter
could have.
&&i64 still auto-derefs for method calls — s.len() works fine on a
&&String, which is why the string filter’s predicate compiles. It does
not auto-deref for operators, because operators are trait impls and
the standard library only provides Rem down to one level of reference. Hence
E0369 on n % 2.
Four fixes, and when each is right
// 1. Destructure in the pattern. Zero cost, reads well for Copy types.
nums.iter().filter(|&&n| n % 2 == 0)
// 2. Dereference in the body.
nums.iter().filter(|n| **n % 2 == 0)
// 3. Change the item type before you filter — usually the best answer.
nums.iter().copied().filter(|n| n % 2 == 0)
// 4. Take the slice by value where you can.
nums.iter().copied() -> or accept an owned Vec and use into_iter()
Option 3 is the one to reach for. copied() turns an
Iterator<Item = &T> into an Iterator<Item = T> for T: Copy, and from
that point on everything downstream is one reference simpler. Fixing the
item type once beats fixing every closure that touches it.
The second E0277 is the same story at the other end of the chain. collect()
builds a Vec<String> and you are handing it &Strings. cloned() is
copied() for T: Clone — it calls .clone() on each item.
The naive fix is also rejected — and that is the good part
Everyone’s first instinct is .map(|x| *x) or .map(|x| x.clone()). Try it.
Clippy’s default-on map_clone catches both:
error: you are using an explicit closure for copying elements
help: consider calling the dedicated `copied` method
error: you are using an explicit closure for cloning elements
help: consider calling the dedicated `cloned` method
And on the String side, .map(|s| *s) does not even get that far:
error[E0507]: cannot move out of `*x` which is behind a shared reference
*x on a &i64 is a copy — legal, because i64: Copy. *s on a
&String is a move out of a borrow — illegal, always, because moving
would leave the caller’s vector holding a hole. Same syntax, opposite
outcomes, and the difference is Copy. If you understand why those two lines
differ, you understand most of what Copy is for.
copied or cloned?
Both exist, and using the wrong one is not a compile error — cloned() works
on Copy types too, since every Copy type is Clone. Prefer copied
when the type is Copy: it says at the call site that the operation is a
bitwise copy and cannot run arbitrary user code. There is a clippy lint for
this (cloned_instead_of_copied), but it is pedantic and off by default, so
the gate will not catch it for you here. Do it because it is true, not because
you were forced.
Where to put the clone
On the String side, order matters for a reason clippy will tell you about:
names.iter().cloned().filter(|s| s.len() >= 4).collect()
error: unnecessarily eager cloning of iterator items
[clippy::iter_overeager_cloned]
That version clones every name and then throws most of them away. Filter first, clone the survivors. The lint is default-on, so this one is graded.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.