Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

Rust辞典

  1. トップページ
  2. Rust辞典
  3. Fn / FnMut / FnOnce トレイト

Fn / FnMut / FnOnce トレイト

Rustのクロージャは、キャプチャの方法によって『Fn』『FnMut』『FnOnce』の3つのトレイトのいずれかを実装します。関数の引数としてクロージャを受け取る際はこれらのトレイト境界を使って型を指定します。

構文
// Fn — 不変参照でキャプチャ。何度でも呼び出せます。
fn call_fn<F: Fn() -> i32>(f: F) -> i32 { f() }

// FnMut — 可変参照でキャプチャ。何度でも呼び出せますが mut が必要です。
fn call_fnmut<F: FnMut() -> i32>(mut f: F) -> i32 { f() }

// FnOnce — 所有権を奪ってキャプチャ。1度しか呼び出せません。
fn call_fnonce<F: FnOnce() -> i32>(f: F) -> i32 { f() }

// dyn Fn() を使った動的ディスパッチ
fn call_dyn(f: &dyn Fn()) { f(); }
トレイト一覧
トレイト概要
Fn不変参照(&self)でキャプチャ。繰り返し呼び出し可能。FnMut・FnOnce を包括します。
FnMut可変参照(&mut self)でキャプチャ。繰り返し呼び出し可能。FnOnce を包括します。
FnOnce所有権(self)を奪ってキャプチャ。1度しか呼び出せません。
move クロージャキャプチャした変数の所有権をクロージャに移動します(スレッド渡しなどに使います)。
サンプルコード
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

fn apply_mut<F: FnMut() -> i32>(mut f: F) -> i32 {
    f() + f()  // 2回呼び出します。
}

fn apply_once<F: FnOnce() -> String>(f: F) -> String {
    f()  // 1度だけ呼び出します。
}

fn main() {
    // Fn — キャプチャした値を不変で参照します。
    let base = 10;
    let add_base = |x| x + base;  // base を &base で借用します。
    println!("Fn: {}", apply(add_base, 5));   // 15
    println!("再呼び出し可能: {}", apply(|x| x * 2, 7));  // 14

    // FnMut — キャプチャした値を可変で参照します。
    let mut count = 0;
    let mut counter = || {
        count += 1;  // count を &mut count で借用します。
        count
    };
    println!("FnMut 1回目: {}", counter());  // 1
    println!("FnMut 2回目: {}", counter());  // 2
    println!("FnMut 3回目: {}", counter());  // 3

    // apply_mut — カウンターを2回呼び出します。
    let mut n = 0;
    let result = apply_mut(|| { n += 1; n });
    println!("apply_mut 結果: {}", result);  // 2

    // FnOnce — 所有権を奪うため1度しか呼べません。
    let name = String::from("Alice");
    let greet = move || format!("Hello, {}!", name);  // name の所有権を奪います。
    println!("{}", apply_once(greet));  // Hello, Alice!
    // greet はもう呼び出せません。

    // move クロージャ — スレッドへの渡し方
    let data = vec![1, 2, 3];
    let handle = std::thread::spawn(move || {
        // data の所有権をスレッドに移動します。
        println!("スレッド内: {:?}", data);
    });
    handle.join().unwrap();

    // dyn Fn() による動的ディスパッチ
    let funcs: Vec<Box<dyn Fn(i32) -> i32>> = vec![
        Box::new(|x| x + 1),
        Box::new(|x| x * 2),
        Box::new(|x| x * x),
    ];
    for f in &funcs {
        print!("{} ", f(3));  // 4 6 9
    }
    println!();
}
概要

Rustのクロージャは周囲の変数を「借用(不変)」「借用(可変)」「所有権の移動」の3方法でキャプチャし、それぞれ『Fn』『FnMut』『FnOnce』トレイトを実装します。3つのトレイトは包含関係にあり、『Fn』を実装するクロージャは『FnMut』と『FnOnce』も自動で実装します。

関数の引数型として『Box<dyn Fn()>』を使うと異なる型のクロージャを同じコレクションに格納できます(動的ディスパッチ)。パフォーマンスが必要な場合はジェネリクス『<F: Fn()>』を使うと静的ディスパッチになりインライン化されます。

スレッドにクロージャを渡す場合は『move』を付けて所有権を移動させる必要があります。詳細はstd::thread::spawn() / join()を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。