카테고리 없음

Rust 반복자(Iterator)와 컬렉션 조작 고급 기법

mystory55801 2025. 6. 4. 15:46

Rust에서 반복자(Iterator)란 무엇인가요?

Rust의 반복자(Iterator)는 컬렉션 데이터를 하나씩 처리할 수 있는 강력하고 유연한 기능입니다. 반복자는 데이터를 지연 평가(lazy evaluation) 방식으로 처리하기 때문에 성능상 이점이 크며, 함수형 프로그래밍 스타일로 데이터를 조작할 수 있게 해 줍니다.

Rust에서 반복자는 Iterator 트레잇을 구현하며, 가장 핵심적인 메서드는 next()입니다. 이 메서드는 반복자가 순차적으로 요소를 반환하도록 정의합니다.


fn main() {
    let nums = vec![10, 20, 30];
    let mut iter = nums.iter();

    while let Some(val) = iter.next() {
        println!("Value: {}", val);
    }
}

이터레이터 어댑터: map, filter, fold의 활용

Rust의 반복자는 다양한 이터레이터 어댑터를 제공합니다. 이 어댑터들은 반복자를 새로운 반복자로 변환하며, 체이닝 방식으로 연쇄적으로 사용할 수 있습니다.

  • map: 각 요소에 함수 적용
  • filter: 조건에 맞는 요소만 선택
  • fold: 누적값 계산 (reduce와 유사)

fn main() {
    let nums = vec![1, 2, 3, 4, 5];

    let result: i32 = nums.iter()
        .map(|x| x * 2)
        .filter(|x| x % 3 == 0)
        .fold(0, |acc, x| acc + x);

    println!("최종 결과: {}", result);
}

이 코드는 다음과 같은 작업을 수행합니다: 2배 → 3의 배수만 선택 → 합산. Rust에서는 이러한 조작이 매우 간결하고 안전하게 이루어집니다.

이터레이터 체이닝으로 복잡한 로직 간단하게 표현

이터레이터는 체이닝을 통해 복잡한 데이터 흐름을 간단하게 구성할 수 있습니다. 예를 들어 텍스트 라인에서 특정 단어가 포함된 줄만 추출하고, 이를 대문자로 바꾸는 작업도 아래와 같이 처리할 수 있습니다.


fn main() {
    let lines = vec!["hello rust", "rust is fast", "I love systems"];

    let result: Vec<_> = lines.into_iter()
        .filter(|line| line.contains("rust"))
        .map(|line| line.to_uppercase())
        .collect();

    println!("{:?}", result);
}

이터레이터 체이닝은 불필요한 중간 변수를 줄이고, 데이터 흐름을 함수형 스타일로 표현할 수 있게 해 줍니다.

into_iter vs iter vs iter_mut

Rust에서 컬렉션을 반복할 때 사용하는 메서드는 3가지가 있습니다:

  • iter(): 참조를 반환 (&T)
  • iter_mut(): 가변 참조 반환 (&mut T)
  • into_iter(): 소유권을 이동 (T)

fn main() {
    let mut nums = vec![1, 2, 3];

    for n in nums.iter() {
        println!("읽기 전용: {}", n);
    }

    for n in nums.iter_mut() {
        *n *= 2;
    }

    for n in nums.into_iter() {
        println!("소유권 이동 후 출력: {}", n);
    }
}

올바른 반복 방식 선택은 메모리 안전성과 성능에 직결되므로 반드시 이해하고 사용하는 것이 중요합니다.

enumerate, zip, chain 등의 고급 이터레이터 활용

Rust는 다양한 고급 반복자 어댑터를 제공합니다:

  • enumerate(): 인덱스와 값을 함께 반환
  • zip(): 두 반복자를 병렬로 결합
  • chain(): 반복자를 이어 붙임
  • skip(), take(): 일부 요소 건너뛰거나 제한

fn main() {
    let a = vec![1, 2, 3];
    let b = vec![4, 5, 6];

    for (i, val) in a.iter().enumerate() {
        println!("{}번째 요소: {}", i, val);
    }

    let zipped: Vec<_> = a.iter().zip(b.iter()).collect();
    println!("zip 결과: {:?}", zipped);

    let chained: Vec<_> = a.iter().chain(b.iter()).collect();
    println!("chain 결과: {:?}", chained);
}

이터레이터 커스터마이징: 직접 반복자 구현하기

Rust에서는 Iterator 트레잇을 직접 구현하여 사용자 정의 반복자도 만들 수 있습니다.


struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Self {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

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

fn main() {
    for num in Counter::new() {
        println!("카운터: {}", num);
    }
}

이런 방식으로 반복 로직을 캡슐화하고 재사용 가능한 형태로 구성할 수 있습니다.

결론: 이터레이터는 Rust의 성능과 표현력을 동시에 높여주는 도구

Rust의 Iterator 시스템은 고성능을 유지하면서도 함수형 프로그래밍의 장점을 누릴 수 있도록 도와주는 핵심 기능입니다. map, filter, fold 같은 어댑터와 함께, enumerate, zip, chain 등 고급 기능을 활용하면 복잡한 데이터 처리도 깔끔하게 구현할 수 있습니다.

또한 반복자의 지연 평가 특성은 메모리 사용량을 줄이고, 필요한 순간에만 계산하도록 해 성능 최적화에도 매우 효과적입니다. 실무 Rust 프로젝트에서도 반복자는 꼭 익히고 활용해야 할 핵심 도구입니다.