컬렉션을 다루다 보면 반복문이 길어지기 쉽습니다. Rust의 이터레이터와 클로저를 사용하면 동일한 로직을 더 짧고 선언적으로 표현할 수 있습니다. 이번 편에서는 Iterator 트레이트의 핵심 메서드, 클로저 문법, 그리고 둘을 조합한 데이터 처리 예제를 다룹니다.
클로저 문법부터 익히기
클로저는 주변 스코프의 값을 캡처할 수 있는 익명 함수입니다.
fn main() {
let threshold = 50;
let is_high = |score: i32| score > threshold;
println!("70점은? {}", is_high(70));
}
|score: i32| score > threshold가 클로저입니다.- 타입을 생략하면 컴파일러가 추론해 줍니다.
- 캡처 방식은 클로저 본문이 값을 어떻게 쓰는지에 따라 move, 불변 참조, 가변 참조 중 하나가 자동으로 선택됩니다. 필요하면
move키워드를 붙여 소유권을 옮길 수 있습니다.
처음에는 아래처럼 이해하면 충분합니다.
- 읽기만 하면 보통 불변 참조로 캡처
- 수정하면 가변 참조로 캡처
- 클로저 밖으로 값을 넘겨야 하면
move가 필요할 수 있음
Iterator 트레이트의 핵심
- 모든 이터레이터는
next메서드를 구현합니다. - 표준 컬렉션(
Vec,HashMap,Range등)은iter,iter_mut,into_iter메서드로 이터레이터를 만듭니다. - 체이닝 가능한 어댑터 메서드(
map,filter,take,collect등)를 제공해 파이프라인을 구성합니다.
fn main() {
let scores = vec![45, 67, 88, 52];
let high_scores: Vec<_> = scores
.iter()
.filter(|score| **score >= 60)
.map(|score| format!("합격: {}", score))
.collect();
println!("{:?}", high_scores);
}
iter()는 불변 참조 이터레이터를,into_iter()는 값을 소비하는 이터레이터를 만듭니다.collect()는 원하는 컬렉션 타입으로 결과를 모읍니다. 위 예제에서는Vec<String>으로 추론됩니다.
다만 collect()는 결과 타입이 모호하면 컴파일러가 추론하지 못할 수 있습니다. 그럴 때는 let result: Vec<_> = ...처럼 결과 타입을 명시해 주면 됩니다.
소유권과 이터레이터
iter()는&T,iter_mut()는&mut T,into_iter()는T를 반환합니다.- 가변 이터레이터에서 값을 수정하면 원본 컬렉션에 바로 반영됩니다.
fn main() {
let mut names = vec!["yuna".to_string(), "min".to_string()];
names.iter_mut().for_each(|name| name.make_ascii_uppercase());
println!("{:?}", names);
}
사용자 정의 이터레이터 만들기
struct Counter {
current: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Self { current: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current >= self.max {
None
} else {
self.current += 1;
Some(self.current)
}
}
}
fn main() {
let sum: u32 = Counter::new(5).map(|n| n * 2).sum();
println!("sum = {}", sum);
}
type Item으로 이터레이터가 내놓는 값의 타입을 정의합니다.sum,product같은 소비자 메서드는 이터레이터를 끝까지 순회하며 결과를 반환합니다.
클로저와 에러 처리 결합하기
fn parse_numbers(lines: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError> {
lines
.iter()
.map(|line| line.trim().parse::<i32>())
.collect()
}
collect()는Result<Vec<_>, E>형태를 자동으로 추론합니다. 실패하면 즉시Err를 반환하고, 성공하면 모든 값을 모읍니다.
CodeSandbox로 이어서 실습하기
아래 샌드박스는 CodeSandbox의 Rust starter입니다. 이번 글의 핵심 코드를 src/main.rs에 옮기고, cargo check와 cargo run 결과를 나란히 보면서 컴파일 메시지와 실행 출력을 비교해 보세요.
💬 댓글
이 글에 대한 의견을 남겨주세요