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

Joseph Halfmoon

前回は「ありがちな」エレトステネスの篩を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 日本語版」様の以下のページなどを参考にさせていただいております。

forループ

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関数を実行してみた結果が以下に。どれも「回って」ます。

loopsResults

再帰であれば、mut なしでかなりイケそうな気がしないでもないです。でもデカい構造をスタックにおいて再帰すると、Rustでもスタックあふれることがあるみたいだし。当然か。

やっつけな日常(41) Rustに入ればRustに従え、配列の大きさ、usizeって何よ? へ戻る

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