Rustプログラミング言語の3章練習問題をやってみた

技術,プログラミング言語,練習問題

TL;DR

プログラミング言語Rustの3章練習問題をやってみました。Rust初心者なので回答内容は保証しませんが、参考まで。

個人的Rustブーム再び

2021年3月末で会社を辞めた後、色々新しいことをやろうと考えて、一度Rustに手を出したことがあります。その後、Go言語の方がなんとなくフィーリングが合う感じがしてRustは放置状態に。

しかし、最近個人的にWebAssemblyに注目し始めたこともって、マイRustブームが再燃してます。そんなわけで以前に途中まで読んだ「The Rust Programming Language 日本語版」を頭から読み直してみたり。

せっかくなのでたまに出てくる練習問題を解いてみたので、参考までに公開しておきます。何しろ初心者なので大したソースではないですし、きっともっと良い回答が世の中にあるとは思います。まあ、場末のブログがネタにこまった末だと思ってもらえれば良いかと。

第3章

まだ3章で難しいことをやっているわけでもないので、特に解説的なことは不要かな、と思います。

練習1.温度の摂氏と華氏を変換する

これはちょっと検索して調べてしまいました。と言っても、プログラムの中身を知りたいと言うより、どういうプログラムを作っているのだろう?というもの。設問がざっくりしすぎててどうしようかとちょっと途方に暮れてしまいまいた。

結局、最初にどちらの変換かを訊いて、次に温度を入力するという回答例を見つけたのでその仕様をいただきました。元のプログラムも2章の数当てゲームを参考にしたと書いてありましたが、同じようにしています。メッセージは日本語にしましたけど。

// 温度を華氏と摂氏で変換する
use std::io;

fn main() {
    loop {
        // 1.最初に摂氏から華氏か、華氏から摂氏かを入力
        println!("何をしますか?(1:摂氏→華氏、2:華氏→摂氏、0:終了)");
        
        let mut op = String::new();
        io::stdin()
            .read_line(&mut op)
            .expect("入力がうまくできませんでした。");

        let op = op.trim();

        match op {
            "1" => (),
            "2" => (),
            "0" => break,
            _ => {
                println!("0,1,2のいずれかを入力してください。");
                continue;
            },
        }

        // 2.次に温度の数値を入力
        println!("温度を入力してください。");
        let mut degree = String::new();
        io::stdin()
            .read_line(&mut degree)
            .expect("温度の入力がうまくできませんでした。");
        let degree: f64 = match degree.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        // 3.計算結果を出力
        let mut converted = 0.0;
        let mut unit = ("", "");
        match op {
            "1" => {
                converted = degree * 1.8 + 32.0;
                unit = ("°C", "°F");
            },
            "2" => {
                converted = (degree - 32.0) / 1.8;
                unit = ("°F", "°C");
            },
            _ => (),
        };
        println!("{}{} → {}{}", degree, unit.0, converted, unit.1);
    }
}

最初に何をするか訊く部分(16〜24行目)と計算する部分(40〜48行目)が同じような分岐をしているのがやや気になります。

おっさんなので同じ変数の再宣言しての使い回し(例えば9行目と14行目)は考え方に慣れない部分もありますが、Rust的には積極的に使うべきかと意識するようにはしています。

練習2.フィボナッチ数列のn番目を生成する

これは特に仕様で迷う部分はなかったのですぐにできました。まあ、アルゴリズムはWikipediaで調べたのですが。元はPythonコードかと思いますが、それをほぼベタ移植。

// フィボナッチ数のn番目を求める

fn fibonacci(n: i32) -> i32 {
    let mut f = (1, 0);
    for _ in 1..n {
        (f.0, f.1) = (f.1, f.0 + f.1);
    }
    f.1
}

fn main() {
    // 10番目まで出力
    for i in 1..=10 {
        println!("{} ", fibonacci(i));
    }
}

お題は特定の何番目かの値を出力すれば良いだけです。しかし、それだけでは結果を確認しにくいため、10番目まで出力するようにしています。

8行目はセミコロンのつけ忘れではなくて不要。付けるとエラーになります。これもおっさんにはちょっと違和感。これは関数の最後の式が返り値になるという仕様に沿った記述。
ただ、個人的にはついセミコロンつけてエラーになったりするので、冗長ですが普通に「return f.1;」と書こうかと思ってます。そっちの方が慣れているので分かりやすいと言うのと、やはり返り値は明示的にreturnと書いた方がわかりやすいのではないかと。もちろん、ワンライナーみたいな関数なら自明なので良いですけど、ある程度の行数がある場合はreturnを明示した方が良いのではないかと考えています。

練習3."The Twelve Days of Christmas"の歌詞を反復性を利用して出力する

これも歌詞をまずは調べるところから始めました。英語圏では有名なのかも知れませんが、日本人にはなじみがないですよね。強いて言うなら「一本でも人参」でしょうか。

// "The Twelve Days of Christmas"の歌詞を、 曲の反復性を利用して出力

fn main() {
    // 歌詞データを準備する
    let days = ["first", "second", "third", "fourth", "fifth",
                "sixth", "seventh", "eighth", "ninth", "tenth",
                "eleventh", "twelfth"];

    let presents = ["partridge in a pear tree", "Two turtle doves",
                    "Three French hens", "Four calling birds",
                    "Five gold rings", "Six geese a-laying",
                    "Seven swans a-swimming", "Eight maids a-milking",
                    "Nine ladies dancing", "Ten lords a-leaping",
                    "Eleven pipers piping", "Twelve drummers drumming"];

    // 歌詞を出力
    for i in 0..12 {
        println!("On the {} day of Christmas, my true love sent to me", days[i]);
        for j in (0..=i).rev() {
            if j == 0 && i == 0 {
                println!("A {}", presents[j]);
            } else if j == 0 && i != 0 {
                println!("And a {}", presents[j]);
            } else {
                println!("{}", presents[j]);
            }
        }
        println!("");
    }
}

最初は構造体にしようかと思ったのですが、構造体はこの先の5章の内容なので3章のデータ型の範囲内であるベクタを使っています。「何番目」と「贈り物」の2つのベクタを用意して処理しています。

17行目に数値をハードコーディングしてしまってますが、練習問題なので許してください。

後から気づきましたが、以下のようにタプルのベクタにした方がわかりやすかったでしょうか。変数ひとつで済みますし。

    let lyrics = [
        ("first", "partridge in a pear tree"),
    ];
    println!("On the {} day of Christmas, my true love sent to me", lyrics[0].0);
    println!("A {}", lyrics[0].1);

環境構築について

Rustのインストール自体は公式サイトのドキュメント通りにやるだけで簡単です。

自分の場合は退職1年前くらいからMacを使うようにしています。そのため、homebrewでもインストールできます。ただし、公式の方法が推奨のようです。違いがあるのか簡単に調べてみました。しかし、特に決め手はありませんでした。homebrewの場合、brewletというアプリを使うとbottleのアップデートを通知してくれるので便利。公式方法でもコマンド一発でアップデートできますけど、手動でチェックする必要があるので忘れがちになります。

なお、あまり空きがないとか、何も手元に入れたくない場合はオンラインのRust開発環境もあります。その名もCodeSandbox。

WebブラウザだけでRustを試すことができるし、VSCodeと連携すれば手元のVSCodeでも開発できます。ただ、VScode連携は自宅のネット環境のせいかちょっとラグを感じました。我が家のネットは無線なのであまり速くないのです。GitHubとも連携できるので本格的に取り組むことができます。

またはRust Playgroundというサービスもあります。こちらは「The Rust Programming Language 日本語版」内からもリンクされている公式のもの。様々な連携はありませんが、ちょっと試してみるにはお手軽です。

他にもPaiza.ioでもRustが使えますね。

Rustのバージョンが1.59.0(2023/1/31現在)と、ちょっと古いのが難点です(2023/1/31時点の最新版は1.67.0)。Playgroundは当然最新版ですし、なんならベータやNightlyなんて最新すぎるバージョンも試せます。CodeSandboxは1.66.1でした(2023/1/31現在)。