Rust 반복자(Iterator)와 컬렉션 조작 고급 기법
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 프로젝트에서도 반복자는 꼭 익히고 활용해야 할 핵심 도구입니다.