捕捉時の型推論

Rustはたいていの場合、型アノテーションなしでも変数を捕捉する方法を臨機応変に選択してくれますが、関数を書く場合にはこの曖昧さは許されません。引数のパラメータとしてクロージャを取る場合、そのクロージャの完全な型はいくつかのtraitsの中の1つを使って明示されなければなりません。どれが使われるかは、捕捉された値でクロージャが何をするかによって決まります。制限の少ない順に並べると、下記の通りです。

  • Fn:参照(&T)によって捕捉するクロージャ
  • FnMut:ミュータブルな参照(&mut T)によって捕捉するクロージャ
  • FnOnce:値(T)によって捕捉するクロージャ

変数ごとに、コンパイラは可能な限り制約の少ない方法でその変数を捕捉します。

例えば、FnOnceというアノテーションの付けられたパラメータを考えてみましょう。これはそのクロージャが&T&mut TもしくはTどれか で捕捉することを指定するものですが、コンパイラは捕捉した変数がそのクロージャの中でどのように使用されるかに基づき、最終的に捕捉する方法を選択することになります。

これは、もし移動が可能であれば、いずれの種類の借用であっても同様に可能だからです。その逆は正しくないことに注意してください。パラメータがFnとしてアノテーションされている場合、変数を&mut TTで捕捉することは許可されません。しかし&Tは許可されます。

以下の例で、FnFnMut、および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`。
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // コピーではなくmoveが起きる型
    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");

        // `mem::drop`を明示的に呼ぶと`farewell`が値で
        // 捕捉される必要性が発生します。よって`FnOnce`が必要になります。
        mem::drop(farewell);
    };

    // クロージャを適用する関数を実行。
    apply(diary);

    // `double`は`apply_to_3`のトレイト境界を満たす。
    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}

参照

std::mem::drop, Fn, FnMut, ジェネリクス, where, FnOnce