前回、mutを減らせなくている話を書いたらば先達の方にツイッターにてお教えいただけました。分かっている人のコードを拝見すると目から鱗だよ。いや~Rustのイテレータを勉強しないと。といいつつ今回はイテレータの前にクロージャであります。どうもイテレータと地続き?みたいだし。でもクロージャってこんな面倒だったんだっけ?
※「やっつけな日常」投稿順 Index はこちら
クロージャ
クロージャという「構造物」に初めてお目にかかったのは、かなりな昔、Schemeという処理系ででした。いまだに下手の横好きSchemeですが、そのときのクロージャの印象は「わりかしフツーに書けるじゃん」といった感じ。舐めてましたな。しかし、Rustで「1インクリメントした整数を返すクロージャを返す関数」を書こうと思ったらもう書けません。なんていったってRustコンパイラ様は厳格デス。
一番簡単なクロージャ
まずは、一番簡単なやつ。fn main()の中で
-
- カウンタ変数 mut なやつを定義
- mut なクロージャの定義の中でカウンタをインクリメントして返す
- 上記のクロージャを呼び出せば、呼び出す毎に+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()); }
「カウンタ」クロージャを返す関数で返されたクロージャ、カウントしてるみたい。いいのか?こんなんで。