# 이터레이터 개념

이터레이터(Iterator)는 반복 가능한(Traversable) 데이터 구조를 순회하거나, 생성할 수 있는 일련의 요소를 차례대로 꺼낼 수 있게 하는 객체 또는 메커니즘을 의미한다. 러스트에서 이터레이터는 `Iterator` 트레이트를 통해 정의되며, 이 트레이트는 기본적으로 `next` 메서드를 요구한다. `next` 메서드는 순회 중인 시퀀스에서 다음 요소를 추출해 `Some(item)` 형태로 반환하거나, 더 이상 순회할 요소가 없을 때 `None`을 반환한다. 이터레이터는 지연(lazy) 평가를 특징으로 하여 실제로 요소를 꺼낼 때까지 연산을 수행하지 않으므로, 대규모 데이터를 효율적으로 처리하거나 무한 시퀀스도 구현할 수 있다.

러스트에서 이터레이터를 생성하는 가장 일반적인 방법은 표준 라이브러리에서 제공하는 컬렉션 타입에 대해 `iter()`, `iter_mut()`, `into_iter()` 메서드를 호출하는 것이다. 예를 들어 벡터 `Vec<T>`에 대해 `iter()`를 호출하면, 벡터의 요소들을 순회하며 불변 참조를 차례대로 반환하는 이터레이터를 생성한다. 반면 `iter_mut()`는 가변 참조를 반환하고, `into_iter()`는 실제로 값을 소유권을 옮기며 순회한다. 어떤 함수를 사용할 것인지는 사용 의도와 필요한 소유권 형태에 따라 결정된다.

이터레이터는 단순히 다음 요소를 꺼내는 기능 외에도, 표준 라이브러리에서 제공하는 여러 가지 어댑터(Adapter) 메서드를 통해 다양한 연산을 연결해 수행할 수 있다. 예컨대 `map`, `filter`, `take`, `skip`, `enumerate` 등의 어댑터를 사용하면, 이터레이터가 반환하는 요소들을 원하는 형태로 가공하거나 특정 조건에 맞는 요소만 걸러낼 수 있다. 이 때 어댑터 메서드는 새로운 이터레이터를 반환하여 체이닝(Chaining)이 가능해지고, 실제로 요소를 소모(Consume)하는 시점, 즉 `for` 문을 사용하거나, `collect`나 `fold` 같은 소비자(Consumer) 메서드를 호출할 때 연산이 실행된다. 이러한 특성 때문에 지연(lazy) 평가가 가능하며, 불필요한 중간 연산을 최소화한다.

러스트에서 이터레이터를 실제로 사용하는 가장 흔한 예시는 `for` 문이다. `for` 문에 이터레이터를 직접 넘기면 매 이터레이션(iteration)마다 이터레이터의 `next` 메서드가 호출되어 요소를 꺼내 온다. 다음 예시는 벡터를 순회하는 간단한 코드이다.

```
fn main() {
    let numbers = vec![10, 20, 30];
    for num in numbers.iter() {
        println!("{}", num);
    }
}
```

이터레이터를 조금 더 직접적으로 다루고 싶다면 `Iterator` 트레이트를 사용해 사용자 정의 이터레이터를 작성할 수 있다. `next` 메서드를 구현하면 임의의 로직으로 값을 생성하거나 어떤 자료구조를 순회하는 이터레이터를 만들 수 있다. 예시는 아래와 같다.

```
struct Counter {
    current: u32,
    end: u32,
}

impl Counter {
    fn new(end: u32) -> Self {
        Counter { current: 0, end }
    }
}

impl Iterator for Counter {
    type Item = u32;

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

fn main() {
    let mut counter = Counter::new(5);
    while let Some(num) = counter.next() {
        println!("{}", num);
    }
}
```

위 예시에서 `Counter`는 시작값(`current`)을 0으로 두고, 매번 `next`를 호출할 때마다 1씩 증가시키다가 `end` 값에 도달하면 `None`을 반환하여 반복을 중단한다. 이처럼 간단한 형태의 사용자 정의 이터레이터를 정의해보면, 이터레이터의 동작 방식을 명확하게 이해할 수 있다.

이터레이터의 또 다른 특징은 소모(Consume)와 변환(Transform) 과정이 명확히 구분된다는 점이다. `map`이나 `filter` 같은 변환 어댑터는 지연(lazy)된 새로운 이터레이터를 반환하며, 중간에 여러 변환 단계를 거치더라도 최종적으로 소비자 메서드를 호출할 때에만 실제 연산이 수행된다. 아래 예시는 체이닝과 소비가 함께 일어나는 전형적인 구조를 보여준다.

```
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let sum_of_even_squares: i32 = numbers
        .iter()
        .map(|x| x * x)
        .filter(|x| x % 2 == 0)
        .sum();  // sum()은 소비자 메서드로 실제로 이터레이터를 소모하며 연산 수행

    println!("{}", sum_of_even_squares);
}
```

위 예시에서 `map`으로 모든 원소를 제곱하고, `filter`로 짝수만 추려내며, 최종적으로 `sum()`으로 합산한다. 모든 변환은 `map`과 `filter`가 반환하는 이터레이터 단계에서 지연되다가, `sum`이 호출되는 순간에 차례대로 요소를 꺼내 실제 계산을 수행한다. 이 방식을 통해 메모리를 아낄 수 있고, 불필요한 중간 결과가 만들어지지 않는다.

이터레이터를 통해 얻을 수 있는 장점은 코드 가독성과 메모리 효율성, 그리고 함수형 프로그래밍의 장점을 쉽게 활용할 수 있다는 점이다. 함수형 스타일의 체이닝을 통해 읽기 쉽고 유지보수가 편리한 코드를 작성할 수 있다. 또한 모든 요소를 한 번에 메모리에 적재하지 않고 필요에 따라 하나씩 꺼낼 수 있으므로, 대용량 데이터나 무한 시퀀스도 다룰 수 있다.

결론적으로, 이터레이터는 러스트에서 반복과 데이터 처리 로직을 효율적이고 간결하게 표현하기 위한 핵심 메커니즘이다. 컬렉션의 순회, 지연된 변환, 코드 가독성 향상, 메모리 효율성을 모두 만족시키는 도구이므로, 러스트에서 자료구조를 다룰 때는 이터레이터 패턴을 적극적으로 활용하는 것이 권장된다.
