私はRustを5年ほど使ってますが、クロージャーを引数に取るクロージャーは今まで見たことがないですねぇ。
Rustのクロージャーはジェネリクスにできないので、ご質問のコードのように、func
をクロージャーにするのなら、引数f
のクロージャー|i| {}
は&|i| {}
のようにトレイトオブジェクトにするしかなさそうです。
このようにした場合、&|i| {}
は動的ディスパッチという方法で呼び出されます。
もしfunc
をジェネリックな関数にするのなら、引数f
のクロージャーはトレイトオブジェクトにする必要はありません。つまり|i| {}
を直接引数に取れます。その場合f
は静的ディスパッチという方法で呼び出されます。
後者の方がコンパイラーの最適化が効きやすいので、Rustでは後者が好まれる傾向があります。ただ、それと引き換えに柔軟性が失われるので、柔軟性が必要な場合は前者が用いられます。とはいえ、前者と後者の性能差は一般的にはごく僅かで、多くの場合は測定できないレベルだと思います。なので&|i| {}
のようなトレイトオブジェクトを使っても、特に問題はないはずです。
同じようで少しづつ違うけれどその関数内でしか使わない処理をいくつか書くとき、匿名関数を使って実装するという手法を良く使っていて、
単にその関数内でしか使わない処理を表現したいのなら、関数内に関数を定義する方法もあります。
rust
1pub fn outer_func() {
2 この関数はouter_func内だけで使用できる
3 関数なのでジェネリクスにできる(implトレイトを引数に取れる)
4 fn func(f: impl Fn(u8) -> ()) {
5 f(0);
6 }
7
8 func(|_i| {});
9}
クロージャーとの違いは
- クロージャーは自由変数(その外側にある変数)を捕捉(キャプチャー)できるが、関数はできない
- クロージャーはジェネリクスにできないが、関数はできる
なお関数ポインターはFn
などのクロージャーのトレイトを実装しますので、クロージャーが要求されるところに関数ポインターを渡すことができます。以下のようなことも可能です。
rust
1pub fn outer_func() {
2 fn func(f: impl Fn(u8) -> ()) {
3 f(0);
4 }
5
6 fn inner_func(_i: u8) {}
7
8 // impl Fn(u8) -> ()の代わりに関数ポインターfn(u8) -> ()を
9 // 渡すこともできる
10 func(inner_func);
11}
用途によってはこれらも使えるかもしれません。
追記
func
クロージャーの引数f
をクロージャー(impl Fn(u8) -> ()
)ではなくて、関数ポインター(fn(u8) -> ()
)にすることで、トレイトオブジェクトを作らないで済むことを思い出しました。自由変数を捕捉しないクロージャー(いわゆる無名関数)なら、関数ポインターに型強制できるので、そういうクロージャーなら受け付けられます。
rust
1fn outer_func() {
2 // funcクロージャーは引数に関数ポインターをとる
3 let func = |f: fn(u8)| {
4 f(0);
5 };
6
7 // このクロージャーは自由変数を捕捉していないので
8 // 関数ポインターへ型強制できる
9 func(|_i| {});
10
11 // このクロージャーは自由変数(j)を捕捉しているので
12 // 関数ポインターへは型強制できない
13 let j = 10;
14 func(|i| i + j); // error[E0308]: mismatched types
15 // ^^^^^^^^^ expected fn pointer, found closure
16 // note: expected fn pointer `fn(u8)`
17 // found closure `[closure@src/main.rs:15:10: 15:19]`
18}
まとめ
- クロージャーを引数に取るクロージャー(
func
)では、ご質問のコードのように、引数となるクロージャー(f
)をトレイトオブジェクトにする必要がある。(トレイトオブジェクトは柔軟性がある反面、動的ディスパッチによる性能上のオーバーヘッドがあるため、Rustでは一般的に避けられる傾向がある)
- 柔軟性を多少犠牲にできるなら、以下のどちらかの方法で動的ディスパッチを避けることができる
func
側をジェネリックなローカル関数にする。(f
の型をimpl Fn(u8)
にする) その場合、func
は(クロージャーではないので)自由変数を捕捉できなくなる
f
の型を関数ポインター(fn(u8)
)にする。その場合、f
は(クロージャーではないので)自由変数を捕捉できなくなる
まず最後の2つのどちらかにできないかを検討し、無理なら最初の方法(トレイトオブジェクトによる動的ディスパッチ)にするのがいいかもしれません。なお文法上は、最後の関数ポインターを使う方法が、ご質問にある「こう書きたいコード」に一番近くなります。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。