Skip to content

← Closures and Iterators step 8 of 28

Easy Primitives

Trace a lazy map, filter and take pipeline

Build an iterator pipeline that logs each transformation and predicate call. Return the log in execution order.

pub fn trace(nums: Vec<i64>) -> Vec<String>

Use nums.iter(), followed by map, filter and take(3), in that order:

  1. The map closure logs map:<x> using the original number, then yields x * 2.
  2. The filter closure logs filter:<y> using the doubled number, then keeps it if y % 4 == 0.
  3. take(3) limits the output to the first three values that pass the filter.

Consume the pipeline, count its outputs, and append kept:<n> as the final log entry. Return only the log, not the selected numbers. Format integers as ordinary decimal text with no extra spaces.

Inputs are restricted to numbers whose doubles fit in i64. Zero and negative numbers are allowed; negative multiples of four pass the filter too.

Follow one input through both closures

For [1, 2, 4], the complete log is:

map:1
filter:2
map:2
filter:4
map:4
filter:8
kept:2

Mapping and filtering interleave. Each visited input is logged by map and then by filter before processing the next input. Rejected values still generate both entries. Empty input returns ["kept:0"].

Stop after three accepted values

For [2, 4, 6, 8, 10], the doubled values 4, 8 and 12 pass. The log ends with filter:12 followed by kept:3. Inputs 8 and 10 are never mapped or tested.

The limit counts accepted values, not visited inputs. If fewer than three pass, the pipeline visits every input. Moving take before filter would change that behavior.

Repair all parts of the starter

The starter constructs an iterator but never consumes it, so its closures do not run. It also maps each input to (), because Vec::push returns (), and its filter accepts everything without logging. Finally, it never appends a kept entry.

Fix the map closure to log and then return the doubled number. Fix the filter to log and test that number. End the chain with count so it performs the work and gives you n. Append the final entry after the chain is consumed.

The unused-iterator diagnostic identifies the missing consumer. Assigning an unused iterator to _ may silence a warning, but it does not consume it or repair the result. Passing requires compilation, correct logs and Clippy with warnings denied.

Laziness describes when work happens

Constructing this map/filter/take chain stores the source and closures without calling the mapping or filtering closures. A consumer then drives it. Calling next is one way to request items; consumers can also use specialized iterator methods internally. The exact sequence of internal method calls is not the point of the exercise. The observable log order and stopping position are.

These adapters do not create intermediate collections. The logging closures do allocate strings and grow a vector, so this instrumented pipeline is not an allocation-free computation or a performance benchmark.

Share the log with short borrows

Both closures remain part of the same chain. Capturing a separate &mut borrow of the same vector in each would produce conflicting borrows. The supplied RefCell<Vec<String>> lets both closures share the cell and borrow its contents mutably for each push.

Keep each borrow_mut guard within its logging statement. That releases map's guard before filter logs, avoiding a borrow conflict in this implementation. After consumption, use into_inner to recover the log and append kept.

The map closure receives &i64 from nums.iter(); |&x| copies the integer. After mapping, the item is i64, but filter receives a reference to the item, so |&y| copies the doubled value for its test.