# 이터레이터 어댑터(map, filter, collect)

이터레이터 어댑터는 기존 이터레이터를 조작해 새로운 이터레이터를 반환하거나, 최종 결과물을 생성하는 메서드들을 의미한다. Rust의 이터레이터는 게으르게 동작하므로, 최종적으로 이터레이션을 소비(consumption)하지 않으면 어떠한 연산도 수행하지 않는다. map과 filter 메서드는 기존의 이터레이터를 변형하거나 조건을 적용하는 역할을 수행하고, collect 메서드는 이터레이터를 소비하여 최종적으로 자료구조나 값을 생성한다.

map은 이터레이터가 만들어내는 각 요소에 대해 특정 연산을 적용하고, filter는 조건을 만족하는 요소만 남겨주는 이터레이터 어댑터다. 이 둘은 어떤 데이터를 즉시 변환하거나 제거하는 것이 아니라, 실제로 데이터를 순회하여 결과를 얻는 시점(예: for 루프나 collect, sum, count 등)이 되었을 때 게으른(lazy) 방식으로 변환과 필터링을 수행한다.

collect는 이터레이터를 소비하여 Vec 같은 구체적인 자료구조로 변환한다. collect는 이터레이터에 대한 반복을 내부적으로 수행하고, 각 요소를 원하는 타입의 컨테이너에 쌓는다. collect를 호출하면 게으른 이터레이터 체인이 실제로 동작하면서 모든 요소를 한 번씩 순회하고, 이를 통해 최종 컬렉션 혹은 특정 자료구조가 생성된다. collect는 Rust의 타입 추론(type inference)을 통해 반환 타입을 알아내지만, 종종 구체적인 자료구조 타입을 명시해야 하는 경우도 있다.

아래 예제는 map, filter, collect를 간단하게 조합하여 사용하는 방법을 보여준다.

```
fn main() {
    let numbers = [1, 2, 3, 4, 5, 6];

    // map을 사용하여 각 숫자에 2를 곱하는 새 이터레이터를 생성
    let doubled = numbers.iter().map(|x| x * 2);

    // filter를 사용하여 2로 나눈 나머지가 0인 숫자만 남기는 이터레이터를 생성
    let even_numbers = numbers.iter().filter(|x| *x % 2 == 0);

    // doubled와 even_numbers가 아직 게으른 이터레이터이므로, 명시적으로 순회나 collect를 호출해야 실제 연산이 일어난다
    // collect를 사용하여 doubled를 벡터로 수집
    let doubled_collected: Vec<i32> = doubled.collect();
    println!("2배가 된 숫자들: {:?}", doubled_collected);

    // collect를 사용하여 짝수만 걸러낸 이터레이터를 새로운 벡터로 변환
    let even_collected: Vec<i32> = even_numbers.cloned().collect();
    println!("짝수만 걸러낸 숫자들: {:?}", even_collected);
}
```

map은 이터레이터가 제공하는 모든 요소에 일정한 함수를 적용하여 변환된 값을 내보낸다. map 내부의 클로저가 반환하는 값만큼 이터레이터의 각 요소가 변환되며, 각 변환 결과는 아직 소비되지 않고 다음 어댑터가 적용되기 전까지는 게으르게 평가된다. 위 코드에서 doubled는 numbers.iter().map(|x| x \* 2)를 통해 i32 요소가 2배로 변환된 이터레이터로 만들어지지만, collect를 호출하기 전까지는 실제로 변환이 일어나지 않는다.

filter는 참/거짓을 반환하는 클로저를 인자로 받아, 참이 되는 요소만 남기는 이터레이터를 만든다. 위 예제에서는 짝수를 판별하는 \*x % 2 == 0 조건으로 filter를 적용했다. 이 역시 게으른 이터레이터이므로, collect 등을 호출해야 실제 순회와 조건 검사가 이루어진다.

collect는 이터레이터를 소비하여 최종 결과물을 만드는 대표적인 메서드다. 이터레이터가 만들어 내는 요소들을 하나씩 순회하며 해당 요소를 모아서 벡터나 다른 자료구조에 삽입한다. collect 메서드는 반환 타입을 다양하게 가질 수 있는데, Rust의 타입 추론이 가능하다면 명시적으로 적지 않아도 되지만, 특정 자료구조를 이용하거나 복잡한 체이닝이 있으면 반환 타입을 명시해줘야 한다. 예를 들어 collect::\<Vec<\_>>()처럼 구체적인 타입 매개변수를 지정하여 어떤 자료구조를 만들지 알려줄 수 있다.

이터레이터 체이닝은 map, filter, collect 같은 어댑터와 소비자 메서드를 결합하여 유연하고 간결하게 데이터 변환 과정을 구성할 수 있게 해준다. 중간에 체인을 잘못 구성하거나, 자료구조 소유권 문제로 인해 코드를 옮길 수 없을 때는 참조자(&)를 사용해 반복할 것인지 소유권을 이동하는 값(x)을 직접 다룰 것인지 유의해야 한다. 일반적으로 numbers 같은 배열이나 벡터에 대해 iter() 또는 iter\_mut()를 사용해 참조 기반으로 접근하는 경우가 많고, into\_iter()를 사용하면 소유권이 이동되는 이터레이터가 생성된다.

마지막으로, 게으른 이터레이터에서 map과 filter는 값 자체를 확정짓거나 메모리에 저장하지 않는다. 필요에 따라 즉시 결과를 확인하고자 할 때 collect를 사용해 결과물을 완전히 생성해야 한다. 이 과정을 통해 Rust가 제공하는 함수형 프로그래밍 스타일의 장점을 살리면서 안전성과 효율성도 확보할 수 있다.
