동작하는 코드
Phase 1에서 for 루프로 벡터를 순회했었죠? 같은 작업을 이터레이터로 할 수 있어요.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// for 루프 방식
let mut doubled_loop = Vec::new();
for n in &numbers {
doubled_loop.push(n * 2);
}
// 이터레이터 체인 방식
let doubled_iter: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("for 루프: {:?}", doubled_loop);
println!("이터레이터: {:?}", doubled_iter);
// 둘 다 [2, 4, 6, 8, 10]
}
이터레이터 체인은 "무엇을 하고 싶은지"를 선언적으로 표현해요. 모듈 22에서 배운 클로저가 여기서 빛을 발해요!
직접 해보기
numbers.iter().filter(|n| **n > 3).collect::<Vec<_>>()를 실행해보세요. 결과가 뭔가요?map과filter를 합쳐서 "3보다 큰 수를 2배로" 만들어보세요.
iter() vs into_iter() vs iter_mut()
세 메서드의 차이는 소유권이에요. Phase 2에서 배운 개념이 여기서도 그대로 적용돼요!
| 메서드 | 요소 타입 | 원본 사용 | 비유 |
| ------------- | -------------------- | ------------- | ---------------- |
| iter() | &T (불변 참조) | 가능 ✅ | 책을 읽기만 |
| iter_mut() | &mut T (가변 참조) | 가능 (수정됨) | 책에 메모를 적음 |
| into_iter() | T (소유권 이동) | 불가 ❌ | 책을 가져감 |
fn main() {
// iter() — 빌려서 읽기
let names = vec!["민수", "영희", "철수"];
let lengths: Vec<usize> = names.iter().map(|n| n.len()).collect();
println!("길이: {:?}", lengths);
println!("원본: {:?}", names); // ✅ 아직 사용 가능
// iter_mut() — 빌려서 수정
let mut scores = vec![80, 90, 75];
scores.iter_mut().for_each(|s| *s += 5);
println!("보정 후: {:?}", scores); // [85, 95, 80]
// into_iter() — 소유권 이동
let words = vec![String::from("hello"), String::from("world")];
let upper: Vec<String> = words.into_iter().map(|w| w.to_uppercase()).collect();
println!("대문자: {:?}", upper);
// println!("{:?}", words); // ❌ 소유권이 이동됨
}
팁:
for item in &vec는vec.iter()와 같고,for item in vec는vec.into_iter()와 같아요!
주요 이터레이터 어댑터
map — 변환
각 요소를 다른 값으로 변환해요.
fn main() {
let names = vec!["alice", "bob", "carol"];
let greeting: Vec<String> = names
.iter()
.map(|name| format!("안녕, {name}!"))
.collect();
println!("{:?}", greeting);
// ["안녕, alice!", "안녕, bob!", "안녕, carol!"]
}
filter — 걸러내기
조건을 만족하는 요소만 남겨요.
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evens: Vec<&i32> = numbers
.iter()
.filter(|n| *n % 2 == 0)
.collect();
println!("짝수: {:?}", evens); // [2, 4, 6, 8, 10]
}
enumerate — 인덱스와 함께
요소에 인덱스를 붙여줘요. for 루프에서 인덱스가 필요할 때 유용해요.
fn main() {
let fruits = vec!["사과", "바나나", "포도"];
// for 루프 + 인덱스 변수
let mut i = 0;
for fruit in &fruits {
println!("{i}: {fruit}");
i += 1;
}
// enumerate — 훨씬 깔끔!
for (i, fruit) in fruits.iter().enumerate() {
println!("{i}: {fruit}");
}
}
zip — 두 컬렉션을 쌍으로
두 이터레이터를 하나로 합쳐요.
fn main() {
let names = vec!["민수", "영희", "철수"];
let scores = vec![95, 87, 92];
let report: Vec<String> = names
.iter()
.zip(scores.iter())
.map(|(name, score)| format!("{name}: {score}점"))
.collect();
println!("{:?}", report);
// ["민수: 95점", "영희: 87점", "철수: 92점"]
}
fold — 하나의 값으로 접기
모든 요소를 하나의 값으로 누적해요. 다른 언어의 reduce와 비슷해요.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 합계
let sum = numbers.iter().fold(0, |acc, n| acc + n);
println!("합계: {sum}"); // 15
// 문자열 합치기
let words = vec!["Rust", "는", "멋져요"];
let sentence = words.iter().fold(String::new(), |mut acc, w| {
acc.push_str(w);
acc
});
println!("{sentence}"); // Rust는 멋져요
}
팁: 단순한 합계는
sum()을 쓰는 게 더 간결해요:numbers.iter().sum::<i32>()
for 루프 → 이터레이터 체인 비교
같은 작업을 두 가지 방식으로 비교해볼까요?
과제: 점수 목록에서 60점 이상인 것만 골라 10점 보너스를 더한 합계를 구하기
fn main() {
let scores = vec![45, 82, 67, 93, 55, 78];
// for 루프 방식
let mut total_loop = 0;
for score in &scores {
if *score >= 60 {
total_loop += score + 10;
}
}
// 이터레이터 체인 방식
let total_iter: i32 = scores
.iter()
.filter(|s| **s >= 60)
.map(|s| s + 10)
.sum();
println!("for 루프: {total_loop}");
println!("이터레이터: {total_iter}");
// 둘 다 350
}
이터레이터 체인은 각 단계가 뭘 하는지 한눈에 보여요: 필터(60점 이상) → 변환(+10) → 합계.
"왜?" — 지연 평가
이터레이터 어댑터(map, filter 등)는 지연 평가(lazy evaluation) 돼요. .collect()나 .sum() 같은 소비 어댑터를 호출하기 전까지는 아무것도 실행되지 않아요.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 이 시점에서는 아무 계산도 일어나지 않아요!
let lazy = numbers.iter().map(|n| {
println!("처리 중: {n}");
n * 2
});
println!("--- collect 호출 전 ---");
// collect()를 호출해야 비로소 실행!
let result: Vec<&i32> = lazy.collect();
println!("결과: {:?}", result);
}
실행하면 "--- collect 호출 전 ---"이 먼저 출력되고, 그 다음에 "처리 중: ..."이 출력돼요.
이 덕분에 불필요한 계산을 피할 수 있어요. 예를 들어 filter 뒤에 take(3)을 붙이면, 조건을 만족하는 3개를 찾자마자 나머지는 처리하지 않아요.
연습 1 (쉬움). 아래 for 루프를 이터레이터 체인으로 바꿔보세요.
fn main() {
let words = vec!["hello", "world", "rust"];
// 이것을 이터레이터 체인으로 바꿔보세요
let mut uppercased = Vec::new();
for word in &words {
uppercased.push(word.to_uppercase());
}
println!("{:?}", uppercased);
}
연습 2 (보통). 학생 점수 벡터에서 최고점과 최저점의 차이를 이터레이터 메서드로 구해보세요.
fn score_range(scores: &[i32]) -> Option<i32> {
// 여기를 완성하세요
// 힌트: iter().max()와 iter().min()을 사용
// 둘 다 Option<&i32>를 반환해요
}
fn main() {
let scores = vec![72, 95, 68, 88, 91];
match score_range(&scores) {
Some(range) => println!("점수 범위: {range}"), // 27
None => println!("점수가 없어요"),
}
}
연습 3 (도전). 문자열 벡터에서 5글자 이상인 단어의 개수를 세고, 해당 단어들을 대문자로 변환한 벡터도 만들어보세요. 이터레이터 체인을 사용하세요.
fn main() {
let words = vec!["hi", "hello", "rust", "programming", "code", "iterator"];
// 5글자 이상인 단어 개수: ???
// 5글자 이상 + 대문자: ???
}
Q1. iter()와 into_iter()의 차이는?
- A)
iter()가 더 빠르다 - B)
into_iter()는 불변 참조를 반환한다 - C)
iter()는 빌려서 읽고,into_iter()는 소유권을 가져간다 - D) 차이가 없다
Q2. 아래 코드의 출력은?
fn main() {
let nums = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = nums
.iter()
.filter(|n| **n > 2)
.map(|n| n * 10)
.collect();
println!("{:?}", result);
}
- A)
[10, 20, 30, 40, 50] - B)
[1, 2, 3, 4, 5] - C)
[30, 40, 50] - D)
[3, 4, 5]
Q3. 이터레이터의 "지연 평가"란?
- A) 이터레이터는 느리게 동작한다
- B) 이터레이터는 항상 모든 요소를 먼저 계산한다
- C)
collect()등 소비 어댑터를 호출해야 비로소 계산이 실행된다 - D)
map()과filter()는 즉시 결과를 반환한다