Rust 이터레이터는 zero-cost abstraction의 대표 사례다. 체이닝이 길어도 컴파일러가 단일 루프로 최적화한다.
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]
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)
}
}
자주 쓰지만 잘 모르는 어댑터들:
// 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();
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();
// 슬라이딩 윈도우 어댑터
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]]
// 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 호출
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()가 더 효율적일 수 있음.
// 아래 두 코드는 최적화 후 동일한 기계어 생성
// 방법 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과 같은 원리.
// 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();
// 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())
}