クロージャーとFn/FnMut/FnOnceの使い分け(@Rust)

これもRustのメモリ管理のための特有機能になりますが、クロージャーを使う時にはその引数は、匿名構造体に保存されるようで、クロージャーを受け取るときにその引数がどういう使われ方をするかを指定するためにFn/FnMut/FnOnceの三種類のトレイトが存在します。

Fn/FnMut/FnOnceについての解説は以下のリンクが分かりやすい。

https://qiita.com/hiratasa/items/c1735dc4c7c78b0b55e9

Rust by exampleの以下のリンクから、

https://doc.rust-jp.rs/rust-by-example-ja/fn/closures/input_parameters.html

コードを抜き出すと以下のようになりますが、ここでfn apply()とfn apply_to_3()はお互いに独立しています。

fn apply_to_3()はトレイト境界がシンプルで中で何らの変更もしていないのでFn指定になっています。

// A function which takes a closure as an argument and calls it.
// <f> denotes that F is a "Generic type parameter"
fn apply<f>(f: F) where
    // The closure takes no input and returns nothing.
    F: FnOnce() {
    // ^ TODO: Try changing this to `Fn` or `FnMut`.

    f();
}

// A function which takes a closure and returns an `i32`.
fn apply_to_3<f>(f: F) -> i32 where
    // The closure takes an `i32` and returns an `i32`.
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // A non-copy type.
    // `to_owned` creates owned data from borrowed one
    let mut farewell = "goodbye".to_owned();

    // Capture 2 variables: `greeting` by reference and
    // `farewell` by value.
    let diary = || {
        // `greeting` is by reference: requires `Fn`.
        println!("I said {}.", greeting);

        // Mutation forces `farewell` to be captured by
        // mutable reference. Now requires `FnMut`.
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");

        // Manually calling drop forces `farewell` to
        // be captured by value. Now requires `FnOnce`.
        mem::drop(farewell);
    };

    // Call the function which applies the closure.
    apply(diary);

    // `double` satisfies `apply_to_3`'s trait bound
    let double = |x| 2 * x;

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

Fn/FnMut/FnOnceの三種類のトレイトは、それぞれ以下のようになっていてFnMutはFnOnceをFnはFnMutをそれぞれ継承しています。それぞれの関数の引数を見ると、引数をどのように扱っているかが分かりますが一番過激なのがFnOnce、ただしdropしてしまうので一度しか使えない、変更だけならFnMut、参照するだけならFnというふうになります。

上記のコードでmem::drop(farewell);がなければ、F: FnOnce()はFnMutで問題無いし(ただしfn apply<f>(f: F) whereはfn apply<f>(mut f: F) whereに変更必要)farewell.push_str("!!!");がなければ(つまり引数を変更しなければ)Fnで問題ありません。

pub trait FnOnce
where
    Args: Tuple,
{
    type Output;

    // Required method
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

pub trait FnMut: FnOnce
where
    Args: Tuple,
{
    // Required method
    extern "rust-call" fn call_mut(
        &mut self,
        args: Args
    ) -> Self::Output;
}

pub trait Fn: FnMut
where
    Args: Tuple,
{
    // Required method
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

 

admin

カテゴリーRust