CodeBlanks Logo Icon CodeBlanks

Iterator fusion

Optimize with iterator fusion.

Iterator fusion

Chained adapters don't make separate passes: each element goes through the whole chain in one go. Through inlining, the compiler "fuses" the chain into a single loop. This is clearly visible when you observe the order:

let kayit = RefCell::new(Vec::new());
let _ = (1..=3)
    .map(|x| { kayit.borrow_mut().push(format!("m{x}")); x * 2 })
    .filter(|x| { kayit.borrow_mut().push(format!("f{x}")); *x > 2 })
    .count();
// m1 f2 m2 f4 m3 f6 — element by element, not all maps first

Since no intermediate collection is created, memory use is constant too.

collect in between

Putting a collect in the middle of a chain breaks the fusion: all elements are written to a vector, then read again. Use an intermediate collection only when really needed (sorting, multiple passes):

// a needless intermediate vector
let ara: Vec<_> = v.iter().map(|x| x * 2).collect();
let t: i32 = ara.iter().filter(|x| **x > 2).sum();

// fused
let t: i32 = v.iter().map(|x| x * 2).filter(|x| *x > 2).sum();

Practice Observe the fusion

Write the example above: add mX inside map and fX inside filter to a RefCell<Vec<String>> log; consume the chain with count(). Print the log joined with spaces.

Starter code

main.rs
use std::cell::RefCell;

fn main() {
    // buraya yaz
}

Sample solution

main.rs
use std::cell::RefCell;

fn main() {
    let kayit = RefCell::new(Vec::new());
    let _adet = (1..=3)
        .map(|x| {
            kayit.borrow_mut().push(format!("m{x}"));
            x * 2
        })
        .filter(|x| {
            kayit.borrow_mut().push(format!("f{x}"));
            *x > 2
        })
        .count();
    println!("{}", kayit.borrow().join(" "));
}

Expected output

m1 f2 m2 f4 m3 f6

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why was <code>RefCell</code> needed for the log?

  • It wasn't needed
  • For threads
  • Two closures want to change the same vector; two &mut can't be captured at once Correct answer
  • For speed

Explanation: Each closure only captures &RefCell; the change is made at runtime, with short borrows.

2 Shared log

Question
let kayit = ____::new(Vec::new());
// iki closure da kayit.borrow_mut() ile yazar
Answer
let kayit = RefCell::new(Vec::new());
// iki closure da kayit.borrow_mut() ile yazar

Explanation: RefCell lets two closures change the same vector in turn.

3 Chain and number of passes

Term Meaning
map + filter + sum 1 pass
map + collect + filter + sum 2 passes
Work that needs sorting An intermediate collection is required
take(2) Stops early

All lessons in Expert