입력 매개변수로서의 클로저
Rust 는 대개 타입 주석 없이 변수를 캡처하는 방법을 자동으로 결정하지만, 함수를 작성할 때는 이러한 모호성을 허용하지 않습니다. 클로저를 입력 매개변수로 받을 때는 클로저의 전체 타입을 몇 가지 트레이트 중 하나를 사용하여 주석으로 표시해야 합니다. 이 트레이트는 클로저가 캡처한 값을 참조, 가변 참조 또는 값으로 사용하는 방식에 따라 결정됩니다. 제약 조건이 강한 순서대로 다음과 같습니다.
Fn: 클로저가 캡처한 값을 참조 (&T) 로 사용합니다.
FnMut: 클로저가 캡처한 값을 가변 참조 (&mut T) 로 사용합니다.
FnOnce: 클로저가 캡처한 값을 값 (T) 으로 사용합니다.
변수별로 컴파일러는 가능한 가장 제약이 적은 방식으로 변수를 캡처합니다.
예를 들어, 매개변수가 FnOnce로 주석 처리된 경우, 클로저는 &T, &mut T 또는 T로 캡처할 수 있지만, 컴파일러는 클로저 내에서 캡처된 변수가 사용되는 방식에 따라 최종적으로 선택합니다.
이는 이동이 가능하면 모든 종류의 빌드도 가능해야 하기 때문입니다. 반대의 경우는 사실이 아닙니다. 매개변수가 Fn으로 주석 처리된 경우, 변수를 &mut T 또는 T로 캡처하는 것은 허용되지 않지만, &T는 허용됩니다.
다음 예제에서 Fn, FnMut, FnOnce의 사용 순서를 바꿔보고 무슨 일이 발생하는지 확인해 보세요.
// 클로저를 인수로 받아 호출하는 함수.
// <F>는 F 가 "제네릭 타입 매개변수"임을 나타냅니다.
fn apply<F>(f: F) where
// 클로저는 입력을 받지 않고 아무것도 반환하지 않습니다.
F: FnOnce() {
// ^ TODO: 이것을 `Fn` 또는 `FnMut` 으로 변경해 보세요.
f();
}
// 클로저를 받아 `i32` 를 반환하는 함수.
fn apply_to_3<F>(f: F) -> i32 where
// 클로저는 `i32` 를 받아 `i32` 를 반환합니다.
F: Fn(i32) -> i32 {
f(3)
}
fn main() {
use std::mem;
let greeting = "hello";
// 복사할 수 없는 타입.
// `to_owned` 는 빌려진 데이터에서 소유 데이터를 만듭니다.
let mut farewell = "goodbye".to_owned();
// 2 개의 변수를 캡처합니다: `greeting` 은 참조로, `farewell` 은 값으로.
let diary = || {
// `greeting` 은 참조입니다: `Fn` 이 필요합니다.
println!("I said {}.", greeting);
// 변수 변경은 `farewell` 이 가변 참조로 캡처되도록 강제합니다. 이제 `FnMut` 이 필요합니다.
farewell.push_str("!!!");
println!("Then I screamed {}.", farewell);
println!("Now I can sleep. zzzzz");
// 수동으로 drop 을 호출하면 `farewell` 이 값으로 캡처되도록 강제합니다. 이제 `FnOnce` 가 필요합니다.
mem::drop(farewell);
};
// 클로저를 적용하는 함수를 호출합니다.
apply(diary);
// `double` 은 `apply_to_3` 의 트레이트 바운드를 충족합니다.
let double = |x| 2 * x;
println!("3 doubled: {}", apply_to_3(double));
}