맞아요! 러스트의 클로저(Closure)는 고계 함수(Higher-Order Function, HOF)와 밀접한 관계가 있어요.
고계 함수란?
map, filter, fold 같은 이터레이터 메서드에서 클로저를 자주 사용해요. 클로저란?
fn 함수와 달리, 선언된 환경의 변수들을 캡처할 수 있어요. |x| x + 1 같은 간결한 문법으로 사용할 수 있어요. 러스트에서 고계 함수는 보통 클로저를 인자로 받아요. 예제를 볼까요?
fn apply_function<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
fn main() {
let add_one = |x| x + 1;
let result = apply_function(add_one, 5);
println!("Result: {}", result); // 6
}
설명
apply_function은 고계 함수예요. (F라는 제네릭 타입을 받아서 함수 f를 실행) add_one = |x| x + 1; → 클로저 (익명 함수) apply_function(add_one, 5); → add_one(5) 실행 즉, 클로저는 고계 함수에서 활용할 수 있는 익명 함수예요!
클로저는 선언된 환경의 변수를 캡처할 수 있어요.
fn main() {
let factor = 3;
let multiply = |x| x * factor; // factor를 캡처!
println!("{}", multiply(4)); // 12
}
설명
multiply 클로저는 factor 변수를 외부에서 캡처했어요. fn 함수에서는 불가능한 동작이지만, 클로저는 환경을 캡처할 수 있어요. Fn, FnMut, FnOnce)러스트 클로저는 변수를 캡처할 때 세 가지 방식이 있어요.
| 트레잇 | 설명 |
|---|---|
FnOnce | 한 번만 호출 가능 (소유권 이동) |
FnMut | 변경 가능 (가변 참조) |
Fn | 읽기 전용 (불변 참조) |
fn main() {
let msg = String::from("Hello");
let consume = || println!("{}", msg); // msg의 소유권을 이동
consume();
// consume(); // ❌ ERROR: msg의 소유권이 이동했으므로 재사용 불가
}
consume()을 한 번 호출하면 msg의 소유권이 이동해서 다시 사용할 수 없어요. fn main() {
let mut counter = 0;
let mut increment = || counter += 1; // 가변 참조
increment();
increment();
println!("Counter: {}", counter); // 2
}
FnMut은 가변 변수(mut)를 수정할 수 있는 클로저예요. fn main() {
let numbers = vec![1, 2, 3];
let sum: i32 = numbers.iter().map(|x| x * 2).sum();
println!("Sum: {}", sum); // 12
}
Fn은 단순히 값을 읽기만 해요. numbers.iter().map(|x| x * 2) → 클로저는 x를 읽지만 변경하지 않아요. 둘 다 함수처럼 동작하지만 차이가 있어요.
| 특징 | 일반 함수 (fn) | 클로저 (` |
|---|---|---|
| 환경 캡처 | ❌ 불가능 | ✅ 가능 |
| 타입 추론 | ❌ 필요 (i32 명시) | ✅ 자동 추론 |
| 사용법 | 명확한 시그니처 필요 | 간결한 문법 사용 가능 |
fn add_one_fn(x: i32) -> i32 {
x + 1
}
fn main() {
let add_one_closure = |x| x + 1; // 클로저
println!("{}", add_one_fn(5)); // 6
println!("{}", add_one_closure(5)); // 6
}
fn 함수는 명확한 시그니처 필요 (i32 명시) |x| x + 1만 써도 됨) 러스트에서 클로저는 이터레이터 API와 함께 자주 사용돼요.
filter와 map을 활용한 클로저fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let even_numbers: Vec<i32> = numbers.iter()
.filter(|&x| x % 2 == 0) // 클로저 사용
.map(|x| x * 2)
.collect();
println!("{:?}", even_numbers); // [4, 8]
}
.filter(|&x| x % 2 == 0) → 짝수만 필터링 .map(|x| x * 2) → 각 숫자를 2배로 변환 Fn, FnMut, FnOnce)Rust에서 함수형 프로그래밍 스타일을 활용하고 싶다면 클로저와 고계 함수를 잘 이해하는 것이 중요해요!