입력 파라미터로 사용
Rust는 대부분 타입 어노테이션 없이 즉석에서 변수를 캡처하는 방식을 선택하지만, 함수를 작성할 때는 이러한 모호함이 허용되지 않습니다. 클로저를 입력 파라미터로 받을 때, 클로저의 전체 타입은 몇 가지 트레이트 중 하나를 사용하여 어노테이션되어야 하며, 이는 클로저가 캡처된 값으로 무엇을 하는지에 따라 결정됩니다. 제약이 강한 순서대로 나열하면 다음과 같습니다:
Fn: 클로저가 캡처된 값을 참조(&T)로 사용합니다FnMut: 클로저가 캡처된 값을 가변 참조(&mut T)로 사용합니다FnOnce: 클로저가 캡처된 값을 값(T)으로 사용합니다
변수별로 컴파일러는 가능한 가장 제약이 적은 방식으로 변수를 캡처합니다.
예를 들어, FnOnce로 어노테이션된 파라미터를 생각해 보세요. 이는 클로저가 &T, &mut T, 또는 T로 캡처할 수 있음을 나타내지만, 컴파일러는 최종적으로 클로저 내에서 캡처된 변수가 어떻게 사용되는지에 따라 선택합니다.
이동(move)이 가능하다면 어떤 유형의 빌림(borrow)도 가능해야 하기 때문입니다. 그 반대는 성립하지 않음에 유의하세요. 파라미터가 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 = "안녕";
// 복사되지 않는 타입.
// `to_owned`는 빌려온 데이터로부터 소유권이 있는 데이터를 생성합니다.
let mut farewell = "잘 가요".to_owned();
// 두 개의 변수를 캡처합니다: `greeting`은 참조로, `farewell`은 값으로.
let diary = || {
// `greeting`은 참조에 의한 방식입니다: `Fn`이 필요합니다.
println!("저는 {}라고 말했습니다.", greeting);
// 수정을 위해 `farewell`을 가변 참조로 캡처해야 합니다.
// 이제 `FnMut`가 필요합니다.
farewell.push_str("!!!");
println!("그러고 나서 저는 {}라고 소리쳤습니다.", farewell);
println!("이제 잘 수 있겠네요. zzzzz");
// 수동으로 drop을 호출하면 `farewell`을 값으로
// 캡처해야 합니다. 이제 `FnOnce`가 필요합니다.
mem::drop(farewell);
};
// 클로저를 적용하는 함수를 호출합니다.
apply(diary);
// `double`은 `apply_to_3`의 트레이트 바운드를 만족합니다
let double = |x| 2 * x;
println!("3의 두 배: {}", apply_to_3(double));
}