Skip to content

← Closures and Iterators step 7 of 28

Medium Primitives

Filter borrowed values into owned results

Repair the iterator chains so they filter borrowed inputs into owned results. Keep the supplied signature:

pub fn evens_then_names(nums: &[i64], names: &[String]) -> (Vec<i64>, Vec<String>)

The first output contains every even number from nums, including zero and negative even numbers. Inputs may use the full i64 range. The second output contains every name with at least four UTF-8 bytes, preserving its exact text. Keep input order and duplicates in both outputs. Either input can be empty; its corresponding output is then empty. Do not change the caller's data.

For [1, 2, 3, 4] and ["ab", "abcd", "xyz"], return ([2, 4], ["abcd"]). Length means bytes, not characters or graphemes: "é" has two bytes, "éé" has four, and "🦀" also has four. Whitespace and control characters count toward byte length too.

Track the item type through the chain

nums.iter() yields &i64. The filter predicate receives a reference to each item, so its parameter is &&i64. Filtering decides whether to yield the original item; it does not change that item's type.

Expression Iterator item Filter predicate receives
nums.iter().filter(...) &i64 &&i64
nums.iter().copied().filter(...) i64 &i64
names.iter().filter(...) &String &&String

The starter has two separate problems. Its numeric predicate uses % on &&i64, for which the needed remainder implementation is unavailable. Also, both chains still yield references, while the requested vectors own i64 and String values. Fixing only the predicate leaves the collection errors.

Method calls such as s.len() can automatically dereference the String references. Do not assume that an operator expression follows the same method-lookup rules. For this numeric predicate, dereferencing or a reference pattern makes the intended integer operation explicit.

Copy integers out of the borrowed sequence

This complete example shows three equivalent ways to collect integers:

fn main() {
    let nums = [-2_i64, -1, 0, 1, 2];
    let a: Vec<i64> = nums.iter().filter(|&&n| n % 2 == 0).copied().collect();
    let b: Vec<i64> = nums.iter().filter(|n| **n % 2 == 0).copied().collect();
    let c: Vec<i64> = nums.iter().copied().filter(|n| n % 2 == 0).collect();
    assert_eq!(a, [-2, 0, 2]);
    assert_eq!(a, b);
    assert_eq!(b, c);
    assert_eq!(nums, [-2, -1, 0, 1, 2]);
}

In the first two chains, filter still yields &i64; copied converts the survivors to i64. In the third, copied changes the item before filtering. The predicate still receives a reference, now &i64, which supports this remainder expression. copied removes one reference layer, not every layer that a later adapter might introduce.

Clone only the strings you keep

String is not Copy. Moving a String out through a shared reference is not allowed, so use cloned to obtain owned copies of the selected strings. Filtering first avoids cloning rejected names:

fn main() {
    let names = [String::from("hi"), String::from("🦀"), String::from("hello")];
    let kept: Vec<String> = names.iter().filter(|s| s.len() >= 4).cloned().collect();
    assert_eq!(kept, ["🦀", "hello"]);
    assert_eq!(names, ["hi", "🦀", "hello"]);
}

On an iterator of &T, cloned calls T's Clone implementation. Here T is String. On an iterator of &&String, cloning one layer would instead clone a reference; it would not produce an owned String.

Both copied and cloned work for i64, but copied states that no Clone call is needed. Clone is a trait operation and can have type-specific behavior. See the iterator adapter documentation.

Keep the borrowing API and pass Clippy

The slice references are already passed by value; their elements remain borrowed. Accepting owned Vec inputs would be a different API and is not part of this repair.

Clippy flags explicit copying or cloning map closures with map_clone, such as .map(|n| *n) over &i64 or .map(|s| s.clone()) over &String. Use the dedicated adapters. On the String chain, iter_overeager_cloned flags cloning before this filter. The work saved depends on which inputs fail the predicate, not on an assumed rejection rate.

Passing requires the expected results, compilation and Clippy with warnings treated as errors.