前回は「ありがちな」エレトステネスの篩をRustで練習してみました。しかし気になったのが mut の多さです。小さなプログラムなのに mut と宣言しているところが4か所もありました。普段、Cで書いていたら気にしないのにRustだと mut と書かねばならないのでとても気になります。Rustを作った人たちの思うツボか?
※「やっつけな日常」投稿順 Index はこちら
mutが多すぎた前回のコードを反省
前回のエラトステネスの篩のコードでの ミュータブル変数の多用を反省したコードが以下です。とりあえずループや中間変数で mut していたところを「小手先」で直してみたもの。
// Sieve of Eratosthenes 2 const N: usize = 8190; fn sieve_of_eratosthenes() -> usize { let mut flags: [bool; N + 1] = [true; N + 1]; let mut count: usize = 1; print!("{} ", 2); for i in 0..=N { if flags[i] { let p = i + i + 3; print!("{} ", p); for k in ((i+p)..=N).step_by(p) { flags[k] = false; } count += 1; } } return count; } fn main() { println!("Sieve of Eratosthenes"); println!("----------------------------"); let count = sieve_of_eratosthenes(); println!("\n----------------------------"); println!("Number of primes < {}: {}", 2*N+3, count); }
だいたいRustでまともにループを書いたことがないので、今回は「普通のループ」をいくつかのパターンで試してみることにしました。ループを回る回数を制御する変数と、その中で積算する変数の2つが必要になるもの。
いつもお世話になっております「Rust By Example 日本語版」様の以下のページなどを参考にさせていただいております。
mutはゼロ個の末尾再帰ループ
まずは mut を1個も書かずに済むといえば、再帰(リカージョン)でありましょう。再帰の途上の関数引数に紛れて?ループ回数にせよ、積算にせよ、あからさまな mut は必要ありませぬ。
fn rec_loop(cnt: usize) -> usize { print!("{} ", cnt); if cnt == 0 { return 0; } cnt + rec_loop(cnt - 1) }
一応、Rustも末尾再帰(テイル・リカージョン)をサポートしているみたいですが、どこまでどうしてくれるのかは調べてありませぬ。
forループ使えばイテレータよ
ご存じのとおり、forループでイテレータを使ってrangeを回ればループ回数の制御にあからさまな mut は不要。ただし積算用の変数には mut は必要と。ただ直にforループを回しても何なので、今回は for(int i=0; i<=cnt; i+=2)みたいなSTEPありのforループとしてみました。こんな感じ。
fn for_loop_by_2(cnt: usize) -> usize { let mut sum = 0; for i in (0..=cnt).step_by(2) { print!("{} ", i); sum += i; } sum }
しかし、Pascalでいうところの downto みたいなダウンステップのforはどう書いたらよいのかしら?そういうイテレータがあるような?
whileとloopで、mutバッチリ
条件ループのwhileと無条件ループのloopでは、ループ回数の変数と、積算の変数の両方にmutつける方法しか思いつかなかったです。
まずはwhileから。ことさらに downto してます。
fn while_loop(mut cnt: usize) -> usize { let mut sum = 0; while cnt > 0 { print!("{} ", cnt); sum += cnt; cnt -= 1; } sum }
続いて無限ループのloop。ことさらに break してます。
fn cntdown_loop(mut cnt: usize) -> usize { let mut sum = 0; loop { print!("{} ", cnt); if cnt == 0 { break; } sum += cnt; cnt -= 1; } sum }
どちらにせよ、変数2個に mut バッチリで芸がありませぬな。
実行結果
上記の4関数を実行してみた結果が以下に。どれも「回って」ます。
再帰であれば、mut なしでかなりイケそうな気がしないでもないです。でもデカい構造をスタックにおいて再帰すると、Rustでもスタックあふれることがあるみたいだし。当然か。