Rust에서 이터레이터(iterator)는 값을 순회(iterate)할 수 있도록 해주는 강력하고 유연한 추상화입니다. 이터레이터는 반복 가능한 값을 하나씩 꺼내면서 작업을 수행할 때 사용되며, 지연 평가(lazy evaluation)를 통해 성능도 뛰어납니다.
| Rust에서 지연 평가(lazy evaluation)는 계산이 필요한 시점까지 연산을 연기하는 전략입니다. 즉, 값을 즉시 계산하는 대신, 해당 값이 필요할 때까지 계산을 미루는 방식입니다. 이를 통해 불필요한 계산을 방지하고 성능을 향상시킬 수 있습니다. |
1. 기본 개념
Rust에서 이터레이터는 Iterator 트레잇을 구현한 타입입니다. 이 트레잇은 next() 메서드를 정의합니다.
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
- next()는 Option Enum 형식으로 반환하므로, Some(value)를 반환하다가, 더 이상 값이 없으면 None을 반환합니다.
- for 루프는 내부적으로 이 next()를 호출하여 동작합니다.
가. 예제 1: 기본 사용
fn main() {
let v = vec![10, 20, 30];
let mut iter = v.iter(); // 불변 참조로 이터레이터 생성
while let Some(x) = iter.next() {
println!("값: {x}");
}
}
let v = vec![10, 20, 30]
- v는 정수형 벡터입니다.
- 즉, Vec 타입이고 [10, 20, 30]이라는 세 개의 요소를 가지고 있습니다.
let mut iter = v.iter()
- v.iter()는 벡터 v의 각 요소에 대한 불변 참조 (&i32)를 반환하는 이터레이터를 생성합니다.
- 즉, iter는 &10, &20, &30을 순서대로 반환할 준비가 된 상태입니다.
- iter는 가변 변수로 선언되었습니다(mut) → .next()를 호출할 때 이터레이터 내부 상태를 바꾸기 때문입니다.
while let Some(x) = iter.next() { … }
- .next()는 이터레이터에서 다음 값을 하나씩 꺼냅니다.
- 반환값은 Option<&i32>입니다.
- 값이 있으면 Some(&값)
- 끝나면 None
- while let Some(x)는 Some일 때 루프를 돌고, None이면 종료됩니다.
println!(“값: {x}”);
- x는 &i32 타입이므로 10, 20, 30이 참조 형태로 출력됩니다.

- println!은 참조를 자동으로 역참조해서 출력해주기 때문에 따로 *x를 쓰지 않아도 됩니다.
나. 예제 2: for 루프 사용
fn main() {
let v = vec![1, 2, 3];
for num in v.iter() {
println!("num = {num}");
}
}
while문과 아래가 다릅니다.
for num in v.iter()
- v.iter()는 불변 참조 이터레이터를 생성합니다.
- 즉, &1, &2, &3을 순서대로반환합니다.
- for 루프는 이터레이터의 .next()를 자동으로 반복 호출하여 값을 하나씩 꺼냅니다.
- 변수 num의 타입은 &i32입니다 (참조).
- v.iter()는 벡터를 소유하지 않고 참조만 하므로, v는 이루프 이후에도 여전히 사용 가능합니다.
- println!(“num = {num}”);에 따라 1,2,3이 출력됩니다.
2. 소비(consuming) 어댑터
이터레이터를 사용해 데이터를 소모하는 메서드입니다.
- .sum(): 합계 반환
- .count(): 요소 개수
- .collect(): 컬렉션으로 변환
fn main() {
let v = vec![1, 2, 3, 4, 5];
let sum: i32 = v.iter().sum();
println!("합계: {}", sum);
}
여기서 특이한 점은 sum 다음에 i32라고 타입이 명시되어 있다는 점입니다.
: i32를 빼고 실행하면 “type annotations needed”라고 에러가 발생하는데,

sum이 &i32를 받아서 더할 수는 있지만, 반환값의 형식을 추론할 수 없기 때문에 안정성과 명확성을 추구하는 Rust가 에러를 발생시키는 것입니다.
3. 변형(transforming) 어댑터
이터레이터에서 새로운 이터레이터를 생성하지만 실제 순회는 for, collect 등으로 실행되기 전까지 지연 평가됩니다.
- .map(): 각 요소를 변형
- .filter(): 조건에 맞는 요소만 남김
가. map() 예제
fn main() {
let v = vec![1, 2, 3, 4];
let doubled: Vec<i32> = v.iter()
.map(|x| x * 2)
.collect();
println!("{doubled:?}"); // [2, 4, 6, 8]
}
v.iter()
- 벡터 v에 대해 불변 참조 이터레이터를 생성합니다.
- 반환 타입은 impl Iterator<Item = &i32> → 각 요소는 &1, &2, &3, &4.
.map(|x| x * 2)
- map은 이터레이터의 각 항목에 closure를 적용해 새로운 이터레이터를 만듭니다.
- 여기서 x는 &i32이므로, x * 2는 실제로는 *x * 2와 같은 의미입니다.
- 즉, 값은 다음과 같이 변합니다:
- &1 → 1 * 2 → 2
- &2 → 2 * 2 → 4
- …
- x는 &i32이기 때문에 직접 곱하려면 *x * 2라고 해야 하지만, Rust는 x * 2를 보면 자동으로 역참조(*x) 해주기 때문에 생략 가능합니다.
.collect()
- 이터레이터 결과를 컨테이너 타입(여기서는 Vec)으로 수집합니다.
- 이 부분에서 타입 추론이 불가능할 수 있기 때문에, doubled: Vec<i32>로 타입을 명시했습니다.
println!(“{doubled:?}”);
- {:?}는 벡터를 디버그 형식으로 출력해줍니다.
- 출력 결과는 [2, 4, 6, 8]입니다.
나. filter() 예제
fn main() {
let v = vec![1, 2, 3, 4, 5];
let even: Vec<_> = v.into_iter()
.filter(|x| x % 2 == 0)
.collect();
println!("{:?}", even); // [2, 4]
}
다른 것은 같고 filter 부분만 다른데, x를 2로 나눴을 때 나머지가 0인 것을 % 연산자(나머지 연산자)로 구해서 해당되는 것만 collect(수집)하는 것입니다.
4. 소유권과 이터레이터
이터레이터는 다음 세 가지 방식으로 만들 수 있습니다.
| 메서드 | 설명 |
|---|---|
| .iter() | 불변 참조 이터레이터 |
| .iter.mut() | 가변 참조 이터레이터 |
| .into_iter() | 소유권을 이동하는 이터레이터 |
fn main() {
let mut v = vec![1, 2, 3];
for x in v.iter_mut() {
*x *= 10;
}
println!("{:?}", v); // [10, 20, 30]
}
vector 변수 v를 가변 참조로 선언한 다음,
값을 하나씩 꺼내서 10을 곱한 다음 x에 저장하므로 v 변수가 변경됩니다.
이후 벡터 변수 v를 디버그 포맷으로 출력합니다.
5. 사용자 정의 이터레이터
직접 구조체에 Iterator 트레잇을 구현하여 사용자 정의 이터레이터를 만들 수도 있습니다.
struct Counter {
count: usize,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count <= 5 {
Some(self.count)
} else {
None
}
}
}
fn main() {
let c = Counter::new();
for i in c {
println!("{}", i);
}
}
가. 구조체 정의
struct Counter {
count: usize,
}
usize 타입의 count 필드를 가진 Counter 구조체를 정의합니다.
나. Counter의 new 메소드 구현
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
}
Counter 구조체의 fn new() 메소드를 정의하는데, count 필드의 값을 0으로 초기화합니다.
다. Counter를 위한 Iterator 트레잇 구현
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count <= 5 {
Some(self.count)
} else {
None
}
}
}
Counter 구조체를 위해 Iterator 트레잇을 구현하는데
fn new() 메소드에서 반환값은 Item인데 usize 형식이고,
매번 next()가 호출될 때 count를 1씩 증가시키고, 5보다 작거나 같으면 Some(count)을 반환하고, 그렇지 않으면 None을 반환하여 반복을 종료합니다.
라. 메인 함수
fn main() {
let c = Counter::new();
for i in c {
println!("{}", i);
}
}
Count 구조체의 count 필드를 0으로 초기화한 후 c에 넣고
c를 1씩 증가시키면서 실행하는데 5까지만 출력하고 종료합니다.
Counter::new()로 만든 c는 Iterator를 구현하고 있기 때문에 for 루프에서 사용 가능합니다.
6. 정리
- Iterator는 next()를 통해 요소를 하나씩 반환합니다.
- .map, .filter 등은 지연 평가(lazy evaluation) 방식으로 동작합니다.
- .collect()나 for 루프 등을 통해 실제로 실행됩니다.
- 반복 가능한 자료형은 대부분 이터레이터를 제공합니다 (Vec, HashMap, Range 등)
- Rust의 함수형 프로그래밍 스타일을 구성하는 핵심 개념입니다.