Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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
}

세 가지 클로저 트레이트 (캡처 방식)

컴파일러는 클로저가 외부 데이터를 어떻게 다루느냐에 따라 다음 세 트레이트 중 하나를 자동으로 할당합니다.

  1. Fn: 데이터를 읽기 전용으로 빌림 (가장 보편적, 여러 번 호출 가능)
  2. FnMut: 데이터를 가변적으로 빌림 (데이터 수정 가능, 여러 번 호출 가능)
  3. 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 소유권)를 명심하세요.
  • 복잡한 루프 로직은 반복자 체이닝을 통해 버그가 끼어들 틈을 줄이는 것이 좋습니다.