0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rust でネストしたクロージャを関数の戻り値にする

Posted at

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 TraitBox を組み合わせる
  • (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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?