본문으로 건너뛰기

2-3. iterator closure

· 6분 읽기

Day 18-20: Iterator & Closure

참고: The Rust Book Chapter 13 (Iterators and Closures)


1. 이터레이터 3종류 — 소유권 기반

let nums = vec![1, 2, 3];
 
// iter() → &T (읽기 전용, 원본 유지)
for n in nums.iter() { }       // n: &i32
println!("{:?}", nums);        // ✅ nums 살아있음
 
// iter_mut() → &mut T (수정 가능, 원본 유지)
let mut nums = vec![1, 2, 3];
for n in nums.iter_mut() { }   // n: &mut i32
println!("{:?}", nums);        // ✅ nums 살아있음
 
// into_iter() → T (소유권 이동, 원본 소비)
for n in nums.into_iter() { }  // n: i32
// println!("{:?}", nums);     // ❌ nums 소비됨

선택 기준

상황메서드결과 타입원본
읽기만iter()&T유지
수정 필요iter_mut()&mut T유지
소유권 이동into_iter()T소비

Phase 1의 최소 권한 원칙: & < &mut < 소유권 이동


2. 클로저 (Closure)

기본 문법

// Python: lambda x: x * 2
// Rust:
let double = |x| x * 2;
let double = |x: i32| -> i32 { x * 2 };  // 타입 명시 (선택)

클로저 vs 함수: 외부 변수 캡처

let multiplier = 3;
 
// ✅ 클로저: 외부 변수 캡처 가능
let multiply = |x| x * multiplier;
println!("{}", multiply(5));  // 15
 
// ❌ fn: 외부 변수 접근 불가
fn multiply_fn(x: i32) -> i32 {
    x * multiplier  // 컴파일 에러!
}

캡처 방식 — 최소 권한 자동 적용

// 1. &로 캡처 (읽기만 할 때)
let name = String::from("Rust");
let greet = || println!("{}", name);
greet();
println!("{}", name);  // ✅ 살아있음
 
// 2. &mut로 캡처 (수정할 때)
let mut count = 0;
let mut inc = || count += 1;
inc();
println!("{}", count);  // ✅ 살아있음 (값: 1)
 
// 3. move로 캡처 (소유권 이동, 명시적)
let name = String::from("Rust");
let greet = move || println!("{}", name);
greet();
// println!("{}", name);  // ❌ 소유권 이동됨

3. 이터레이터 체이닝

핵심 패턴: filter → map → collect

let nums = vec![1, 2, 3, 4, 5];
 
let result: Vec<i32> = nums.iter()
    .filter(|x| **x >= 3)      // 조건 필터링
    .map(|x| x * 10)           // 변환
    .collect();                 // Vec으로 수집
// [30, 40, 50]

filter에서 이중 참조 주의

nums.iter()           // &i32를 줌
    .filter(|x| ...)  // x는 &&i32 (참조의 참조!)
 
// 해결 방법
.filter(|x| **x >= 3)     // 방법 1: 역참조
.filter(|&&x| x >= 3)     // 방법 2: 패턴으로 벗기기

map에서는 산술 연산 시 자동 역참조가 되어 * 없이도 동작

Python과 비교

# Python
result = [x * 10 for x in nums if x >= 3]
 
# Python (map/filter 버전)
result = list(map(lambda x: x * 10, filter(lambda x: x >= 3, nums)))
// Rust
let result: Vec<i32> = nums.iter()
    .filter(|x| **x >= 3)
    .map(|x| x * 10)
    .collect();

4. 주요 이터레이터 메서드

집계

let nums = vec![1, 2, 3, 4, 5];
 
nums.iter().sum::<i32>()     // 15 (합계, 타입 명시 필요)
nums.iter().count()          // 5 (개수)

검색

nums.iter().any(|x| *x > 3)       // true (하나라도 만족?)
nums.iter().all(|x| *x > 0)       // true (전부 만족?)
nums.iter().find(|x| **x > 3)     // Some(&4) (첫 번째 매칭)
nums.iter().position(|x| *x > 3)  // Some(3) (인덱스)

find, position은 값이 없을 수 있으므로 Option 반환

enumerate — 인덱스 포함

let names = vec!["Alice", "Bob", "Carol"];
 
for (i, name) in names.iter().enumerate() {
    println!("{}: {}", i, name);
}
// 0: Alice
// 1: Bob
// 2: Carol
# Python 동일
for i, name in enumerate(names):
    print(f"{i}: {name}")

zip — 두 컬렉션 짝짓기

let names = vec!["Alice", "Bob"];
let scores = vec![85, 92];
 
let paired: Vec<_> = names.iter().zip(scores.iter()).collect();
// [("Alice", 85), ("Bob", 92)]

flat_map — 중첩 펼치기

let sentences = vec!["hello world", "rust is fun"];
 
// map → 중첩
let nested: Vec<Vec<&str>> = sentences.iter()
    .map(|s| s.split_whitespace().collect())
    .collect();
// [["hello", "world"], ["rust", "is", "fun"]]
 
// flat_map → 평탄화
let flat: Vec<&str> = sentences.iter()
    .flat_map(|s| s.split_whitespace())
    .collect();
// ["hello", "world", "rust", "is", "fun"]
# Python 동일
flat = [word for s in sentences for word in s.split()]

format! — 문자열 포맷팅

let name = "Rust";
let version = 2026;
let msg = format!("{}: ${}", name, version);  // "Rust: $2026"
# Python 동일
msg = f"{name}: ${version}"

5. 클로저를 함수 파라미터로 받기

fn vs Fn

// fn 포인터: 외부 변수 캡처 불가
fn apply(value: i32, func: fn(i32) -> i32) -> i32 {
    func(value)
}
 
// Fn 트레이트: 캡처하는 클로저도 받을 수 있음
fn apply(value: i32, func: impl Fn(i32) -> i32) -> i32 {
    func(value)
}
 
fn main() {
    let multiplier = 3;
    let result = apply(5, |x| x * multiplier);  // Fn이면 ✅
    println!("{}", result);  // 15
}

Fn 트레이트 3종류

트레이트캡처 방식호출의미
Fn&로 캡처여러 번읽기만
FnMut&mut로 캡처여러 번수정 가능
FnOncemove로 캡처한 번만소유권 이동

실무 기본: Fn으로 시작 → 필요하면 FnMutFnOnce


6. ⚠️ Python 개발자 주의사항

PythonRust차이
lambda x: x * 2|x| x * 2문법만 다름
for x in list: 후 list 사용 가능into_iter() 쓰면 원본 소비소유권 주의
list(map(...)).map(...).collect()collect()로 수집 필수
enumerate(list)list.iter().enumerate()메서드 체이닝
이중 for 리스트 컴프리헨션.flat_map()평탄화
sum(list).iter().sum::<i32>()타입 명시 필요

7. 핵심 원칙 정리

  1. iter 선택: 읽기 → iter(), 수정 → iter_mut(), 소비 → into_iter()
  2. 클로저 캡처: Rust가 최소 권한으로 자동 결정 (& < &mut < move)
  3. filter 이중 참조: iter().filter()에서 **x 또는 |&&x| 패턴 필요
  4. 체이닝 패턴: .filter().map().collect()가 가장 흔한 조합
  5. 함수 파라미터: 클로저 받으려면 impl Fn 사용 (fn 포인터 아님)
  6. collect 필수: 이터레이터는 lazy, collect()로 실체화