入力パラメータとして
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));
}