Rust でネスト(入れ子)にしたクロージャを関数の戻り値にしてみました。
- Rust 1.66
はじめに
まず、クロージャを関数の戻り値として使う場合の(戻り値の)型としては以下のような表現方法があります。
- (a)
fn
を使う(move クロージャには使えない) - (b)
impl Trait
を使う - (c)
Box
を使う
この 3パターンをそれぞれ実装してみると次のようになります。
サンプル1
// (a)
fn a1() -> fn(i32) -> i32 {
|x| x * 2
}
println!("a1 = {}", a1()(11));
// (b)
fn b1() -> impl Fn(i32) -> i32 {
|x| x * 2
}
println!("b1 = {}", b1()(12));
// (c)
fn c1() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x * 2)
}
println!("c1 = {}", c1()(13));
実行結果1
a1 = 22
b1 = 24
c1 = 26
move クロージャの場合
次に、クロージャ外の変数をクロージャ内で使用する場合です。
外部の変数(下記サンプルの y
)をキャプチャするには、クロージャに move を付ける必要がありますが、(a) は move クロージャには適用できず、(b) か (c) を用いる事になりそうです。
サンプル2
/* コンパイルエラー: closures can only be coerced to `fn` types if they do not capture any variables
fn a2(y: i32) -> fn(i32) -> i32 {
move |x| x * y
}
println!("a2 = {}", a2(2)(21));
*/
// (b)
fn b2(y: i32) -> impl Fn(i32) -> i32 {
move |x| x * y
}
println!("b2 = {}", b2(2)(22));
// (c)
fn c2(y: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |x| x * y)
}
println!("c2 = {}", c2(2)(23));
実行結果2
b2 = 44
c2 = 46
ネストしたクロージャの場合
それでは本題のネストしたクロージャの場合です。
まず、クロージャとしては以下のように普通にネストさせる事が可能です。
let c = |x: i32| move |y: i32| x * y;
println!("c = {}", c(10)(20)); // c = 200
これを関数の戻り値として使う場合、impl Fn(X) -> Y
の Y の部分に impl Fn・・・
は使えないようなので、次のような方法が考えられます。
- (bc)
impl Trait
とBox
を組み合わせる - (cc)
Box
をネストにする
サンプル3
/* コンパイルエラー: `impl Trait` only allowed in function and inherent method return types, not in `Fn` trait return
fn bb() -> impl Fn(i32) -> impl Fn(i32) -> i32 {
|x| move |y| x * y
}
*/
// (bc)
fn bc() -> impl Fn(i32) -> Box<dyn Fn(i32) -> i32> {
|x| Box::new(move |y| x * y)
}
println!("bc = {}", bc()(3)(32));
// (cc)
fn cc() -> Box<dyn Fn(i32) -> Box<dyn Fn(i32) -> i32>> {
Box::new(|x| Box::new(move |y| x * y))
}
println!("cc = {}", cc()(3)(33));
実行結果3
bc = 96
cc = 99
ちなみに、実用性はともかく move クロージャにしなければ fn が使えるので、以下のようにする事も一応は可能でした。(クロージャ外の変数を使えないので微妙ですが)
fn ba() -> impl Fn(i32) -> fn(i32) -> i32 {
|_x||y| y
}
println!("ba = {}", ba()(3)(31)); // ba = 31
最後に、ネストを増やして関数の引数を使った場合の例(impl Trait と Box の組み合わせ)です。
サンプル4
fn bcc(d: i32) -> impl Fn(i32) -> Box<dyn Fn(i32) -> Box<dyn Fn(i32) -> i32>> {
move |a| Box::new(move |b| Box::new(move |c| a * b * c + d))
}
println!("bcc = {}", bcc(2)(3)(4)(5)); // bcc = 62
今回のサンプルコードは https://github.com/fits/try_samples/tree/master/blog/20230106/rust_nested_closure