はじめに
Rustが数年前にプログラミング言語ランキングTop20に入ったというのもあって、特に作りたいものはこれといってないけど、ただただ触ってみたいと思い日本語チュートリアルを触ってみました。
▽ 触ったチュートリアルがこちら
ただ、そのチュートリアルの和訳が機械翻訳みたいで、不自然な表現が多かったり、文字ばかり並んでいて読む気が失せると思ったので、Step2の「数当てゲームのプログラミング」までを私なりにまとめてみようと思います。
(私も復讐がてら書いてます)
※ Rust初心者のため、説明に誤りがあるかもしれません。誤りがある場合はコメントや編集リクエストをお願いします。
※ インストール、ファイル作成、実行のところはスキップします。
※ 詳しく知りたい場合は、チュートリアルの方をご覧ください。
※ コードは元のチュートリアルにあるものをほぼそのままコピペしています。(一部コメントアウトは消しています。)
本題
各パートの見出しは実際のチュートリアルに合わせています。
1. 事始め 1.2 Hello, World!
関数の書き方
Rustでは、関数は以下のように定義します。
fn 関数名(引数) {
}
main関数
C言語と同じで、ファイルを実行するとmain関数が実行されます。
サンプルコード
fn main() {
println!("Hello, world!");
}
ポイント
- Rustはタブではなく、4スペースでインデントする
(VSCodeでTabを間違えて使いましたが、普通に動きました) -
println!の!は、関数ではなくRustのマクロだよというのを表しているらしい (詳しくは第19章でやるとのこと) - (これはチュートリアルには書いてないが、)
print!は終端に改行なし、println!は改行あり - Rustは各コードを
;で終わらせる
2. 数当てゲームのプログラミング
ここでは1以上100以下の乱数(整数)を当てるという簡単なゲームを作ります。

Step1: 入力処理を実装する
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
- 入力を受け付けるには、C言語のようにstdという標準ライブラリ内のio(入出力)ライブラリをスコープに入れる必要があるので、
use std::io;を入れる。(デフォルトで標準ライブラリ内に定義されている一部のアイテムをスコープに取り込む(ここで入るアイテムをpreludeという)が、足りない場合はuseでスコープに入れる必要がある) - ユーザーの入力を格納するための変数が
let mut guess = String::new();
Rustの変数について
Rustでは変数をletで宣言しますが、デフォルトが不変(immutable)なので
let num = 5
としてしまうと定数として定義されてしまいます。(JavaScriptでいうconstと同じ)
そこで、可変(mutable)にしたい場合は変数名の前にmutをつけます。
let mut num = 5
あとでやりますが、引数に変数を渡す場合も渡した先の処理が可変として扱う場合は宣言時にmutをつけていても、渡すところで再度mutをつける必要があります。(可変の状態で渡す)
-
String::new()はString型の関連関数new。「関連関数」とは「ある型に対して実装される関数」のことです。このnew関数は空の文字列を作成します - 入力は
ioモジュールのstdinを使います - 今回は
use std::io;でioライブラリをインポートしているので、io::stdin()となりますが、インポートしていなかった場合でもstd::io::stdinとすれば使えます。この関数はターミナルの標準入力へのハンドルを表すstd::io::Stdin型のインスタンスを返します - 標準入力は
read_lineメソッドで行います。.read_lineの引数には入力された文字列の保存先の変数を渡します。今回はguess変数に保存させますが、ここでポイントなのが、mutで可変に設定しておくことです。ここで可変にしないと.read_lineがguess変数の内容を変更できません。また、何度もメモリにデータをコピーしないようにするために参照&を使います。 -
read_lineメソッドは入力内容を渡された変数に入れますが、同時にResult値も返す。Resultは列挙型(enum)の一種で、複数の状態のうちの1つになれる型。(取りうる状態を列挙子(variant)と呼ぶ。enumについては第6章で詳しくやるらしい) -
Resultの列挙子はOkとErrで、それぞれ処理の成功と失敗を意味しています。Okの中には正常に生成された値が入っており、Errの中には処理が失敗した過程や理由に関する情報が入っています -
Resultのインスタントにはexpectメソッドが用意されていて、値がErrの場合に引数で渡されたメッセージを表示します。Okの場合はexceptメソッドがOk列挙子が保持している戻り値を返します - 今回のコードでは
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
としていますが、
io::stdin().read_line(&mut guess).expect("Failed to read line");
のように改行しなくても大丈夫です。
-
println!("You guessed: {guess}");の{ }はplaceholderと呼ばれ、変数の値を表示するときに使うものです。式の計算結果を表示する場合は
let x = 5;
let y = 10;
println!("x = {x} and y + 2 = {}", y + 2);
のように空の{ }を置いて、その外にカンマ区切りのリスト形式で書きます。
Step2: 乱数を生成する
ユーザーが当てる整数を1から100までの乱数で生成させます。乱数生成は標準ライブラリにはないので、randクレートを使います。
クレートはRustのソースコードを集めたもので、ここまで書いてきたコード (実行ファイル) が「バイナリクレート」、randクレートは「ライブラリクレート」
(チュートリアルを読んだ感じ)多分std::ioで言うと、stdがクレート、ioがトレイトと呼ばれるものです。
randクレートを使うにはCargo.tomlの編集とビルドが必要になるので、元のチュートリアルをご覧ください
→ 「乱数を生成する」から説明します
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
-
use rand::Rngで乱数生成用のトレイトをスコープ内に入れます - シード値はOSから得ているみたいです
-
gen_rangeメソッドは乱数生成するメソッドで、Rngトレイトで定義されています - 乱数生成範囲は、
1..=100でも良いですが、1..101と表すこともできます
cargo doc --openを実行すると、ドキュメントがローカルでビルドされてブラウザで見ることができます。
ユーザーが入力した数と答えの数を比較する
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
// --snip--
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
上のコードはコンパイルエラーが出ます。この後修正します。
-
use std::cmp::Ordering;という型をスコープに導入します。Ordering型はenumの一種でLess,Greater,Equalという列挙子を持ちます。2つの値の比較に使用します -
cmpメソッドは2つの値の比較を行って、Ordering列挙型の列挙子を返します。引数に渡された値と比べてどうなのかという結果を出力します。 - 今回は
match式で比較結果を使った条件分岐を行っています。match式の分岐部分を「アーム(腕)」と言います。上のアームから順に検証されます
guessがString型で、比較対象のsecret_numberが数値型なので、コンパイルすると型の不一致が発生してエラーが発生します。(文字列と数値型は比較不可能)
数値型には以下のようなもの (他にもあるみたいです) がありますが、デフォルトはi32です。
| ビット数 | 符号 | 型 |
|---|---|---|
| 32 | あり | i32 |
| 32 | なし | u32 |
| 64 | あり | i64 |
| 64 | なし | u64 |
そこで
// --snip--
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
とします。
追加した
let guess: u32 = guess.trim().parse().expect("Please type a number!");
について説明します。
- Rustでは既に宣言している変数を上書きする (shadowing) ことができるので、
guessを使った値をguessに入れることができます。shadowing は値の型変換をするときによく使われるらしいです。 (これについては第3章で詳しく説明するみたい。) -
trim()は文字列の先頭と末尾の空白、改行文字をすべて削除します。(数値データにするために必要、改行文字は入力確定時のEnterキーで含まれてしまう)
改行文字は
- Windowsの場合、キャリッジリターンと改行が入って
\r\n - それ以外は、
\n
-
parse()で型変換を行います。let guess: u32とすることでRust側に変換先の型を指定しています。ここでu32に変換し、これとsecret_numberを比較しているのでRustは自動でsecret_numberもu32として扱ってくれます -
parse()は数値に変換できる文字に対してしか使えません。つまり、変換ができない文字列に対して使った場合はエラーとしてResult型のErrが発生するので、今回のコードではexpectでエラーメッセージを出力するようにしています
ループで複数回の回答ができるようにする
Rustではループ処理をloopで実装します。
// --snip--
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");
// --snip--
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
}
これではループから抜けられないので、さらにコードを変えます。Rustの場合、ループから抜ける場合はbreakを使います。
// --snip--
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
Ordering::Equalの場合、つまりユーザーが正解の数字を当てた場合ループから抜けられるようになっています。
数字以外の入力を弾く
// --snip--
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
// --snip--
guessの部分をexpectからmatchに変更し、cmpと同じように分岐を作ります。これで変換に成功した場合はOkの中の値をnumとして取り出して返し、Err(_) (_は任意の値を表す) の場合はその後の処理をスキップして次のループに入り、ユーザーに再度入力を求めるようにしています。(continueで次のループに強制的に入ることができます。)
デバッグ用に書いていた正解の数字の出力を削除する
正解の数字が出力されていると、当然ゲームにならないので削除します。
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
おわりに
もしかしたら続きもまとめるかもしれません。(時間があれば...)