要素の捕捉

クロージャはとてもフレキシブルに動作するように出来ています。クロージャにおいて型アノテーションをする必要が無いのは前述の仕組みのためですが、この仕組みのおかげでユースケースに応じて参照を取得したり値そのものを取得したりといった動作が可能になります。クロージャは外側の環境にある要素を、以下の形で取得することができます。

  • 参照:&T
  • ミュータブルな参照:&mut T
  • 値そのもの:T

クロージャは出来る限り参照を取得しようとし、その他2つは必要なときのみ取得します。

fn main() {
    use std::mem;
    
    let color = String::from("green");

    // `color`を出力するためのクロージャ。
    // これは`color`を借用(`&`)し、その借用とクロージャを`print`
    // という名の変数に保持します。
    // 借用は`print`がスコープから出るまで続きます。
    //
    // `println!`は参照を与えれば機能するので、
    // これ以上なにかする必要はありません。
    let print = || println!("`color`: {}", color);

    // 借用を行ったクロージャを呼び出します。
    print();

    // `color`を再びイミュータブルで借用することができます。
    // これはクロージャが`color`に対するイミュータブルな
    // 参照しか保持しないためです。
    let _reborrow = &color;
    print();

    // 最後に`print`を使用した後は移動や再借用が許可されます。
    let _color_moved = color;


    let mut count = 0;
    // `count`をインクリメントするためのクロージャ。`count`と`&mut count`
    // の両方を取ることができますが、後者のほうが制限が少ないため、
    // (訳注: `count`だと`&mut count`と違い、一度しか呼ぶことができない。)
    // そちらを取ります。直後に`count`を借用します。
    //
    // `inc`には`mut`をつける必要があります。なぜならミュータブルな型が
    // 中で使用されているからです。ミュータブルなクロージャは呼ぶたびに
    // 内部変数を変更します。
    let mut inc = || {
        count += 1;
        println!("`count`: {}", count);
    };

    // ミュータブルな借用を使ってクロージャを実行。
    inc();

    // クロージャは後で呼ばれるため、まだ `count` をミュータブルで借用しています。
    // 再借用しようとするとエラーになります。
    // let _reborrow = &count; 
    // ^ TODO: この行のコメントアウトを解除しましょう。
    inc();

    // クロージャはもう`&mut count`を借用する必要がありません。
    // なので、エラーを起こさず再借用することができます。
    let _count_reborrowed = &mut count; 

    
    // コピー不可能な型
    let movable = Box::new(3);

    // `mem::drop`は`T`(ジェネリック型)を取る必要があるため、このクロージャは
    // 参照ではなく値を取ります。その場合、もしもコピー可能な値ならば、
    // 元の値はそのままでコピーのみを取ります。
    // 不可能ならば値そのものを移動させます。
    let consume = || {
        println!("`movable`: {:?}", movable);
        mem::drop(movable);
    };

    // `consume`は変数を消費(開放)するため、一度しか呼び出すことができません。
    consume();
    // consume();
    // ^ TODO: この行のコメントアウトを解除しましょう。
}

バーティカルパイプ(訳注:縦線記号||)の前にmoveを使用することで、キャプチャする変数の所有権を取ることをクロージャに強制します。

fn main() {
    // `Vec`はコピーセマンティクスではない。
    let haystack = vec![1, 2, 3];

    let contains = move |needle| haystack.contains(needle);

    println!("{}", contains(&1));
    println!("{}", contains(&4));

    // println!("There're {} elements in vec", haystack.len());
    // ^ 上の行のコメントアウトを解除すると、コンパイル時エラーになります。
    // これは変数の所有権が移った後の再利用を借用チェッカーが許可しないからです。
    
    // クロージャのシグネチャから`move`を削除すると、クロージャは
    // _haystack_ 変数をイミュータブルで借用するようになります。
    // そのため _haystack_ はまだ利用可能となり、上の行のコメントアウトを
    // 解除してもエラーが発生しなくなります。
}

参照

Box, std::mem::drop