12. 클로저와 반복자: 현대적인 제어 흐름 🟡
학습 목표:
- 주변 환경을 캡처하는 익명 함수인 **클로저(Closure)**의 동작 원리를 배웁니다.
- 컬렉션을 우아하게 순회하는 **반복자(Iterator)**의 강력한 기능을 익힙니다.
- C++ 람다(Lambda) 및
<algorithm>패키지와 비교하여, Rust의 지연 평가(Lazy Evaluation) 기반 반복자 체인이 왜 더 안전하고 효율적인지 이해합니다.
1. 클로저: "문맥을 기억하는 익명 함수"
클로저는 변수에 저장하거나 다른 함수에 인자로 넘길 수 있는 익명 함수입니다. C++의 람다와 유사하지만, 캡처 방식([&], [=])을 매번 지정하지 않아도 컴파일러가 가장 적절한 방식을 자동으로 선택합니다.
- 표기법:
|매개변수| { 로직 }형태를 가집니다. - 자동 캡처: 클로저 내부에서 외부 변수를 사용하면 컴파일러가 이를 감지하여 참조(
&), 가변 참조(&mut), 또는 소유권 이동(move) 중 하나로 캡처합니다.
fn main() { let factor = 2; // 외부 변수 'factor'를 읽기 전용으로 자동으로 캡처함 (&factor) let multiply = |n| n * factor; println!("결과: {}", multiply(5)); // 10 }
세 가지 클로저 트레이트 (캡처 방식)
컴파일러는 클로저가 외부 데이터를 어떻게 다루느냐에 따라 다음 세 트레이트 중 하나를 자동으로 할당합니다.
Fn: 데이터를 읽기 전용으로 빌림 (가장 보편적, 여러 번 호출 가능)FnMut: 데이터를 가변적으로 빌림 (데이터 수정 가능, 여러 번 호출 가능)FnOnce: 데이터를 완전히 소유함 (한 번만 호출 가능, 데이터를 이동시킴)
2. 반복자: "효율적인 데이터 순회 기법"
반복자는 일련의 아이템들에 대해 작업을 수행하는 논리적인 단위입니다.
- 지연 평가 (Lazy Evaluation): 반복자는
collect()나for_each()같은 소비 메서드를 호출하기 전까지는 실제 연산을 수행하지 않습니다. - 함수형 체이닝:
map,filter,fold등을 쇠사슬처럼 엮어서 복잡한 로직을 명확한 선언적 문장으로 표현할 수 있습니다.
fn main() { let nums = vec![1, 2, 3, 4, 5]; // 1. iter(): 참조 반복자 생성 // 2. filter(): 짝수만 선별 // 3. map(): 각 숫자를 제곱 // 4. collect(): 결과를 벡터로 수집 (여기서 실제 연산 발생!) let results: Vec<_> = nums.iter() .filter(|&&x| x % 2 == 0) .map(|x| x * x) .collect(); println!("{results:?}"); // [4, 16] }
🚀 실무 패턴: C++ 루프를 대체하는 반복자
| 기존 C++ 패턴 | Rust 반복자 대응 | 핵심 이점 |
|---|---|---|
for (int i=0; i<n; ++i) | .enumerate() | 인덱스와 값을 동시에 안전하게 획득 |
| 두 배열 병렬 순회 | .zip() | 인덱스 범위 초과(Out of bounds) 위험 원천 차단 |
std::accumulate | .fold() / .reduce() | 초기값과 함께 결과를 하나의 값으로 응축 |
| 중첩 루프 평탄화 | .flat_map() | 여러 층의 컬렉션을 단일 층으로 병합 |
| 슬라이딩 윈도우 | .windows(n) | 연속된 부분 구조(Trend 분석 등) 처리 시 탁월 |
#![allow(unused)] fn main() { // zip과 enumerate의 환상적인 궁합 let names = vec!["Node_A", "Node_B"]; let status = vec![true, false]; for (i, (name, is_ok)) in names.iter().zip(status.iter()).enumerate() { println!("{i}번 장치 {name} 상태: {is_ok}"); } }
💡 실무 팁: 제로 비용 추상화 (Loop Fusion)
반복자 체인이 아무리 길어도, Rust 컴파일러는 이를 고도로 최적화하여 수동으로 짠 for 루프와 대등하거나 심지어 더 빠른 기계어를 생성합니다. 이를 **루프 퓨전(Loop Fusion)**이라고 하며, 불필요한 중간 메모리 할당 없이 단 한 번의 순회로 모든 연산을 마칩니다.
마음 놓고 함수형 스타일을 활용하셔도 성능 손해는 없습니다!
📌 요약
- 클로저는 주변 문맥을 안전하게 캡처하며, 캡처 방식은 컴파일러가 자동 결정합니다.
- 반복자는 지연 평가를 활용해 성능과 가독성을 모두 잡은 현대적인 순회 방식입니다.
.iter(),.iter_mut(), **.into_iter()**의 차이(빌림 vs 소유권)를 명심하세요.- 복잡한 루프 로직은 반복자 체이닝을 통해 버그가 끼어들 틈을 줄이는 것이 좋습니다.