이터레이터 심화 Iterators

Rust 이터레이터는 zero-cost abstraction의 대표 사례다. 체이닝이 길어도 컴파일러가 단일 루프로 최적화한다.

1. Iterator 트레이트 직접 구현

핵심: next() 하나만 구현하면 map, filter, fold, zip, collect 등 수십 개가 공짜로 따라온다.
struct Fibonacci {
    a: u64,
    b: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { a: 0, b: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option {
        let next = self.a + self.b;
        self.a = self.b;
        self.b = next;
        Some(self.a) // 무한 이터레이터 — None 반환 없음
    }
}

// 이제 모든 어댑터 사용 가능
let fibs: Vec = Fibonacci::new()
    .take(10)
    .filter(|&n| n % 2 == 0)
    .collect();
// [2, 8, 34]

size_hint 구현으로 성능 최적화

struct Counter {
    current: usize,
    max: usize,
}

impl Counter {
    fn new(max: usize) -> Self { Counter { current: 0, max } }
}

impl Iterator for Counter {
    type Item = usize;

    fn next(&mut self) -> Option {
        if self.current < self.max {
            self.current += 1;
            Some(self.current)
        } else {
            None
        }
    }

    // size_hint → collect()가 미리 Vec 용량 확보 → 재할당 없음
    fn size_hint(&self) -> (usize, Option) {
        let remaining = self.max - self.current;
        (remaining, Some(remaining)) // (lower, upper)
    }
}

2. 어댑터 체이닝 심화

자주 쓰지만 잘 모르는 어댑터들:

// scan — 상태 유지하며 변환 (fold의 이터레이터 버전)
let running_sum: Vec = (1..=5)
    .scan(0, |acc, x| {
        *acc += x;
        Some(*acc) // [1, 3, 6, 10, 15]
    })
    .collect();

// peekable — 다음 원소를 소비하지 않고 미리 보기
let mut iter = [1, 2, 3].iter().peekable();
if iter.peek() == Some(&&1) {
    println!("starts with 1");
}
iter.next(); // 이제야 소비

// flat_map — map + flatten
let words = vec!["hello world", "foo bar"];
let chars: Vec<&str> = words.iter()
    .flat_map(|s| s.split_whitespace())
    .collect();
// ["hello", "world", "foo", "bar"]

// chain — 두 이터레이터 이어붙이기
let a = [1, 2, 3];
let b = [4, 5, 6];
let combined: Vec<_> = a.iter().chain(b.iter()).collect();

// zip — 쌍으로 묶기
let keys = ["a", "b", "c"];
let vals = [1, 2, 3];
let map: HashMap<_, _> = keys.iter().zip(vals.iter()).collect();
Lazy evaluation: 이터레이터 체인을 구성한다고 실제 연산이 일어나지 않는다. collect(), for_each(), sum() 같은 소비 어댑터(consuming adaptor)가 호출되는 순간에만 실행된다.

실데이터 처리 파이프라인

struct Log { level: &'static str, msg: String, ts: u64 }

let logs: Vec = get_logs();

// 에러 로그만 골라 최신순 5개 메시지 추출
let recent_errors: Vec<&str> = logs.iter()
    .filter(|l| l.level == "ERROR")
    .rev()                              // 역순 (최신이 앞으로)
    .take(5)
    .map(|l| l.msg.as_str())
    .collect();

3. 커스텀 어댑터

// 슬라이딩 윈도우 어댑터
struct Windows {
    iter: I,
    window: Vec,
    size: usize,
}

impl Windows
where
    I: Iterator,
    I::Item: Clone,
{
    fn new(mut iter: I, size: usize) -> Self {
        // 첫 윈도우 채우기
        let window: Vec<_> = (&mut iter).take(size).collect();
        Windows { iter, window, size }
    }
}

impl Iterator for Windows
where
    I: Iterator,
    I::Item: Clone,
{
    type Item = Vec;

    fn next(&mut self) -> Option> {
        if self.window.len() < self.size {
            return None;
        }
        let result = self.window.clone();
        // 슬라이딩: 맨 앞 제거, 새 원소 추가
        self.window.remove(0);
        if let Some(next_item) = self.iter.next() {
            self.window.push(next_item);
        }
        Some(result)
    }
}

// 사용
let data = vec![1, 2, 3, 4, 5];
let wins: Vec<_> = Windows::new(data.into_iter(), 3).collect();
// [[1,2,3], [2,3,4], [3,4,5]]

4. IntoIterator & FromIterator

// for 루프의 실제 동작
for x in vec![1, 2, 3] { println!("{x}"); }

// 위는 아래와 동일:
let mut iter = vec![1, 2, 3].into_iter(); // IntoIterator::into_iter()
while let Some(x) = iter.next() {
    println!("{x}");
}
// collect()는 FromIterator 트레이트 사용
// 타입 어노테이션이 어떤 컨테이너로 모을지 결정
let v: Vec             = (0..5).collect();
let s: HashSet         = (0..5).collect();
let d: VecDeque        = (0..5).collect();
let s: String               = ['h','i'].iter().collect();

// 커스텀 타입에 FromIterator 구현
struct WordCount(HashMap);

impl FromIterator for WordCount {
    fn from_iter>(iter: T) -> Self {
        let mut map = HashMap::new();
        for word in iter {
            *map.entry(word).or_insert(0) += 1;
        }
        WordCount(map)
    }
}

let text = "the cat sat on the mat the cat";
let counts: WordCount = text.split_whitespace()
    .map(String::from)
    .collect(); // WordCount::from_iter 호출

5. iter() / iter_mut() / into_iter() 차이

iter()

불변 참조 &T를 yield
컬렉션을 소비하지 않음

for x in v.iter() {
    // x: &i32
}
// v 여전히 유효

iter_mut()

가변 참조 &mut T를 yield
원소 수정 가능

for x in v.iter_mut() {
    *x *= 2; // 원본 수정
}

into_iter()

소유권을 가져가서 T를 yield
컬렉션 소비됨

for x in v.into_iter() {
    // x: i32 (소유)
}
// v 사용 불가

성능 차이?

대부분 없음. 컴파일러 최적화 후 동일 코드 생성. &T는 포인터 크기이고 T가 크면 iter()가 더 효율적일 수 있음.

이터레이터 vs 루프 — Zero-Cost

// 아래 두 코드는 최적화 후 동일한 기계어 생성
// 방법 1: 이터레이터
let sum: i32 = (0..1000).filter(|x| x % 2 == 0).sum();

// 방법 2: 루프
let mut sum = 0i32;
for i in 0..1000 {
    if i % 2 == 0 { sum += i; }
}
이터레이터 체인은 인라이닝을 통해 하나의 루프로 합쳐진다. 중간 Vec을 만들지 않는다. Haskell의 stream fusion과 같은 원리.

6. 함수형 패턴

// fold — 가장 일반적인 누산기
let factorial = (1u64..=10).fold(1, |acc, x| acc * x);

// fold로 직접 구현 가능한 것들
let sum:   i32 = v.iter().copied().fold(0, |a, x| a + x);
let max:   i32 = v.iter().copied().fold(i32::MIN, |a, x| a.max(x));
let count: i32 = v.iter().fold(0, |a, _| a + 1);

// partition — 조건 기준으로 두 Vec으로 나눔
let (evens, odds): (Vec<_>, Vec<_>) = (0..10).partition(|x| x % 2 == 0);

// unzip — 쌍의 Vec을 두 Vec으로
let pairs = vec![(1, 'a'), (2, 'b'), (3, 'c')];
let (nums, chars): (Vec<_>, Vec<_>) = pairs.into_iter().unzip();

Result / Option과 이터레이터

// filter_map — None이면 버리고 Some이면 꺼냄
let strings = vec!["1", "two", "3", "four", "5"];
let nums: Vec = strings.iter()
    .filter_map(|s| s.parse().ok())
    .collect();
// [1, 3, 5]

// collect::, _>>() — 하나라도 실패하면 전체 Err
let parsed: Result, _> = ["1", "2", "3"]
    .iter()
    .map(|s| s.parse::())
    .collect();
// Ok([1, 2, 3])

let parsed: Result, _> = ["1", "nope", "3"]
    .iter()
    .map(|s| s.parse::())
    .collect();
// Err(ParseIntError)

// map_while — Err/None 만나면 이터레이터 종료
let results = ["1", "2", "stop", "4"];
let nums: Vec = results.iter()
    .map_while(|s| s.parse().ok())
    .collect();
// [1, 2]
// ? 연산자와 이터레이터 조합
fn parse_and_sum(inputs: &[&str]) -> Result {
    inputs.iter()
        .map(|s| s.parse::())
        .collect::, _>>()   // 실패 시 즉시 반환
        .map(|v| v.iter().sum())
}