やっつけな日常(43) Rustに入ればRustに従え、カウンタ・クロージャを返したいです

Joseph Halfmoon

前回、mutを減らせなくている話を書いたらば先達の方にツイッターにてお教えいただけました。分かっている人のコードを拝見すると目から鱗だよ。いや~Rustのイテレータを勉強しないと。といいつつ今回はイテレータの前にクロージャであります。どうもイテレータと地続き?みたいだし。でもクロージャってこんな面倒だったんだっけ?

※「やっつけな日常」投稿順 Index はこちら

クロージャ

クロージャという「構造物」に初めてお目にかかったのは、かなりな昔、Schemeという処理系ででした。いまだに下手の横好きSchemeですが、そのときのクロージャの印象は「わりかしフツーに書けるじゃん」といった感じ。舐めてましたな。しかし、Rustで「1インクリメントした整数を返すクロージャを返す関数」を書こうと思ったらもう書けません。なんていったってRustコンパイラ様は厳格デス。

一番簡単なクロージャ

まずは、一番簡単なやつ。fn main()の中で

    1. カウンタ変数 mut なやつを定義
    2. mut なクロージャの定義の中でカウンタをインクリメントして返す
    3. 上記のクロージャを呼び出せば、呼び出す毎に+1された値が読める

というもの。カウンタ変数とクロージャの定義が以下に。

let mut count = 0;
let mut inc = || {
    count += 1;
    count
};

そしてクロージャの呼び出しが以下に。

println!("inc 1st. {}", inc());
println!("inc 2nd. {}", inc());
println!("inc 3rd. {}", inc());

これは簡単だけれども、mutな変数がもろ見えでカッコ悪いっす。クロージャを返す関数の中で定義して、クロージャを返したいです。

クロージャを返す関数、ただしmut なし

グズグズやっていたら、クロージャを返す関数は定義できました。ただ変数を書き換えてはないです、与えられた引数に定数123を加えるだけのもの。定義が以下に。

fn gen_t1() -> impl Fn(i32) -> i32{
    |x| -> i32 {x + 123}
}

上記の関数を呼び出して、dutにクロージャを格納、その後使ってみます。こんな感じ。

let dut = gen_t1();
println!("dut 1st. {}", dut(1));
println!("dut 2nd. {}", dut(2));
println!("dut 3rd. {}", dut(3));
カウンタ変数を内蔵するクロージャを返す関数

短い関数ですが、この記述に行き着くまでに大分試行錯誤いたしましたデス。関数の中の変数をクローンしたものがどこか見えないところのクロージャの中に確保され、クロージャを呼び出す度にインクリメントされているハズのもの。ホントか?

fn gen_t2() -> impl FnMut() -> i32{
    let mut i1 : i32 = 0;
    move || -> i32 {i1 += 1; i1}
}

上記の関数を呼んでクロージャを変数に格納し、何度か呼び出してみるものが以下に。

let mut dut2 = gen_t2();
println!("dut2 1st. {}", dut2());
println!("dut2 2nd. {}", dut2());
println!("dut2 3rd. {}", dut2());
全体を通して実行確認

実験用のコード全体が以下に。

fn gen_t1() -> impl Fn(i32) -> i32{
    |x| -> i32 {x + 123}
} 

fn gen_t2() -> impl FnMut() -> i32{
    let mut i1 : i32 = 0;
    move || -> i32 {i1 += 1; i1}
} 

fn main() {
    let mut count = 0;
    let mut inc = || {
        count += 1;
        count
    };
    println!("inc 1st. {}", inc());
    println!("inc 2nd. {}", inc());
    println!("inc 3rd. {}", inc());
    let dut = gen_t1();
    println!("dut 1st. {}", dut(1));
    println!("dut 2nd. {}", dut(2));
    println!("dut 3rd. {}", dut(3));
    let mut dut2 = gen_t2();
    println!("dut2 1st. {}", dut2());
    println!("dut2 2nd. {}", dut2());
    println!("dut2 3rd. {}", dut2());
}

実行結果が以下に。Results

「カウンタ」クロージャを返す関数で返されたクロージャ、カウントしてるみたい。いいのか?こんなんで。

やっつけな日常(42) Rustに入ればRustに従え、ループを回る、mutの個数? へ戻る

やっつけな日常(44) RustにいればRustに従え、NoneとSomeは仲間、anyは別 へ進む