Rust関数をパラメーターとしてどのように渡しますか?


89

関数をパラメーターとして渡すことはできますか?そうでない場合、良い代替手段は何ですか?

いくつかの異なる構文を試しましたが、正しい構文が見つかりませんでした。私はこれができることを知っています:

fn example() {
    let fun: fn(value: i32) -> i32;
    fun = fun_test;
    fun(5i32);
}

fn fun_test(value: i32) -> i32 {
    println!("{}", value);
    value
}

しかし、それは関数をパラメーターとして別の関数に渡していない:

fn fun_test(value: i32, (some_function_prototype)) -> i32 {
    println!("{}", value);
    value
}

回答:


124

もちろんできます:

fn fun_test(value: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}

fn times2(value: i32) -> i32 {
    2 * value
}

fn main() {
    fun_test(5, &times2);
}

これはRustであるため、クロージャーの所有権と存続期間を考慮する必要があります。

TL; DR; 基本的に、クロージャには3つのタイプ(呼び出し可能なオブジェクト)があります。

  1. Fn:キャプチャしたオブジェクトを変更することはできません。
  2. FnMut:キャプチャしたオブジェクトを変更できます。
  3. FnOnce:最も制限されています。呼び出されると、それ自体とそのキャプチャを消費するため、一度だけ呼び出すことができます。

クロージャがFn、FnMut、FnOnceを実装するのはいつですか?を参照してください詳細については

クロージャーのような単純な関数ポインターを使用している場合、キャプチャセットは空であり、Fnフレーバーがあります。

もっと凝ったことをしたい場合は、ラムダ関数を使用する必要があります。

Rustには、Cの場合と同じように機能する関数への適切なポインターがあります。それらの型は、たとえばfn(i32) -> i32です。Fn(i32) -> i32FnMut(i32) -> i32およびFnOnce(i32) -> i32実際に形質です。関数へのポインターは常にこれら3つすべてを実装しますが、Rustにはクロージャーもあり、関数へのポインター(キャプチャセットが空かどうかによって異なります)に変換される場合とされない場合がありますが、これらの特性の一部は実装されます。

したがって、たとえば、上記の例を拡張できます。

fn fun_test_impl(value: i32, f: impl Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn fun_test_dyn(value: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn fun_test_ptr(value: i32, f: fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}

fn times2(value: i32) -> i32 {
    2 * value
}

fn main() {
    let y = 2;
    //static dispatch
    fun_test_impl(5, times2);
    fun_test_impl(5, |x| 2*x);
    fun_test_impl(5, |x| y*x);
    //dynamic dispatch
    fun_test_dyn(5, &times2);
    fun_test_dyn(5, &|x| 2*x);
    fun_test_dyn(5, &|x| y*x);
    //C-like pointer to function
    fun_test_ptr(5, times2);
    fun_test_ptr(5, |x| 2*x); //ok: empty capture set
    fun_test_ptr(5, |x| y*x); //error: expected fn pointer, found closure
}

1
<F:Fn ...>を使用するかどうか(..、f:&Fn ...)に違いがありますが、詳細を知っておく必要がありますか?
エンジェルエンジェル

@AngelAngel:ええと、Fn*特性なので、通常の<T: Trait>vsが(t: &T)適用されます。非ジェネリックソリューションの主な制限は、参照とともに使用する必要があることです。したがってFnOnce、コピーとして渡す必要があるが必要な場合は、汎用スタイルを使用する必要があります。
ロドリゴ2016

5
代わりに、形質のジェネリックを使用することがより慣用的であることに注意してください(つまり、オブジェクト<F: Fn..>の代わりに(f: &Fn...)これが理由である- 。形質オブジェクトは動的ディスパッチを必要としながら、ジェネリック医薬品は、静的なディスパッチになります。
ウラジミールMatveev

3
興味深いことに、インターフェイス(呼び出し元)の観点からFnOnceは、実際には最も一般的な特性です。キャプチャされた状態の読み取り、変更、所有権の取得に関係なく、すべてのクロージャを受け入れます。FnMutより制限があり、キャプチャされたオブジェクトの所有権を取得するクロージャを受け入れません(ただし、状態の変更は可能です)。Fnキャプチャされた状態を変更するクロージャを受け入れないため、最も制限が厳しくなります。したがって&FnfunTestrequiredは、呼び出し元に最大の制限を課し、そのf中で呼び出す方法に最小の制限を提供します。
user4815162342 2017

30

FnFnMutおよびFnOnce、他の回答に概説され、あるクロージャータイプ。スコープを閉じる関数のタイプ。

クロージャの受け渡しとは別に、Rustは次のような単純な(クロージャではない)関数の受け渡しもサポートしています。

fn times2(value: i32) -> i32 {
    2 * value
}

fn fun_test(value: i32, f: fn(i32) -> i32) -> i32 {
    println!("{}", f (value));
    value
}

fn main() {
    fun_test (2, times2);
}

fn(i32) -> i32これが関数ポインタ型です。

本格的なクロージャが必要ない場合は、関数タイプを操作する方が、クロージャの存続期間に対処する必要がないため、多くの場合簡単です。


これは構造体のメソッドで機能しますか?
IvanTemchenko20年

@IvanTemchenkoたぶん?遊ぶためのコードは次のとおりです。play.rust
lang.org/…–ArtemGr20年

それは私が意図したとおりではありません=)自己の状態をキャプチャするdynクロージャを返すための回避策が見つかったので、インスタンス参照を渡す必要はありません...
IvanTemchenko20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.