注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
Rustの学習を始めましょう。 このプロジェクトでは、古典的な初心者向けのプログラミングの問題、数当てゲームを実装します。 これは次のように動作します。 プログラムは1から100までの数字を1つ、ランダムに生成します。 そしてあなたに、数字を予想して入力するよう促します。 予想値を入力すると、大きすぎる、あるいは、小さすぎるといったヒントを出します。 当たったら、おめでとうと言ってくれます。良さそうですか?
この章を通じて、Rust に関するごく基本なことが学べるでしょう。 次の章「シンタックスとセマンティクス」では、それぞれのパートについて、より深く学んでいきます。
新しいプロジェクトを作りましょう。プロジェクトのディレクトリへ行ってください。
hello_world
の時にどのようなディレクトリ構成で、どのように Cargo.toml
を作る必要があったか覚えてますか?
Cargoにはそれらのことをしてくれるコマンドがあるのでした。やってみましょう。
$ cd ~/projects
$ cargo new guessing_game --bin
$ cd guessing_game
cargo new
にプロジェクトの名前と、そしてライブラリではなくバイナリを作るので --bin
フラグを渡します。
生成された Cargo.toml
を確認しましょう。
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["あなたの名前 <you@example.com>"]
Cargoはこれらの情報を環境から取得します。もし間違っていたら、どうぞ修正してください。
最後に、Cargoは「Hello, world!」を生成します。 src/main.rs
を確認しましょう。
fn main() { println!("Hello, world!"); }
Cargoが用意したものをコンパイルしてみましょう。
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
素晴らしい。もう一度 src/main.rs
を開きましょう。全てのコードをこの中に書いていきます。
先に進む前に、Cargoのコマンドをもう1つ紹介させてください。 run
です。
cargo run
は cargo build
のようなものですが、生成した実行可能ファイルを走らせてくれます。
試してみましょう。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/debug/guessing_game`
Hello, world!
いい感じです。
run
コマンドはプロジェクトを細かく回す必要があるときに便利でしょう。
今回のゲームがまさにそのようなプロジェクトです。すぐに試してから次の行動に移るといったことを繰り返していきます。
では作り始めましょう。
数当てゲームで最初にしないといけないのは、プレイヤに予想値を入力させることです。
これを src/main.rs
に書きましょう。
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); }
[訳注] それぞれの文言は
- Guess the number!: 数字を当ててみて!
- Please input your guess.: 予想値を入力してください
- Failed to read line: 行の読み取りに失敗しました
- You guessed: {}: あなたの予想値: {}
の意味ですが、エディタの設定などによっては、ソースコード中に日本語を使うとコンパイルできないことがあるので、英文のままにしてあります。
いろいろと出てきましたね。順に見ていきましょう。
fn main() { use std::io; }use std::io;
これからユーザの入力を取得して、結果を出力するわけですが、それには、標準ライブラリの中にある io
ライブラリが必要です。
Rustは全てのプログラムに、ごく限られたものをデフォルトでインポートしますが、これを「プレリュード」と呼びます。
プレリュードにないものは、直接 use
する必要があります。
なお、2つ目の「プレリュード」、io
プレリュードもあり、もしそれをインポートすると、 io
に関連した多数の有用なものがインポートされます。
fn main() {
すでに見てきたように main()
関数がプログラムのエントリポイントになります。
fn
構文は新たな関数を宣言し、 ()
で引数がないことを示し、 {
が関数本体の始まりです。
返り値の型は書かなかったので、 ()
、つまり空のタプルとして扱われます。
println!("Guess the number!"); println!("Please input your guess.");
前に println!()
が文字列をスクリーンに表示するマクロであることを学びました。
let mut guess = String::new();
少し興味深いものが出てきました。このわずか1行で、様々なことが起こっています。 最初に気付くのは、これが「変数束縛」を作るlet文であることです。 let文は以下の形を取ります。
fn main() { let foo = bar; }let foo = bar;
これは foo
という名前の束縛を作り、それを値 bar
に束縛します。
多くの言語ではこれを「変数」と呼びますが、Rustの変数束縛は少しばかり皮を被せてあります。
例えば、束縛はデフォルトでイミュータブル (不変)です。
ですから、この例ではイミュータブルではなく、ミュータブル(可変)な束縛にするために mut
を使っているのです。
let
は代入の左辺に単に1つの名前を取るのではなく、実際にはパターンを受け取ります。
パターンは後ほど使います。今のところ、すごく簡単ですね。
let foo = 5; // イミュータブル let mut bar = 5; // ミュータブル
ああ、そして //
から行末までがコメントです。Rustはコメントにある全てのものを無視します。
このように let mut guess
がミュータブルな束縛 guess
を導入することを知りました。
しかし =
の反対側、 String::new()
が何であるかを見る必要があります。
String
は文字列型で、標準ライブラリで提供されています。
String
は伸長可能でUTF-8でエンコードされたテキスト片です。
::new()
という構文ですが、これは特定の型に紐づく「関連関数」なので ::
を使っています。
つまりこれは、 String
のインスタンスではなく、 String
自体に関連付けられているということです。
これを「スタティックメソッド」と呼ぶ言語もあります。
この関数は新たな空の String
を作るので、 new()
と名付けられています。
new()
関数はある種の新たな値を作るのによく使われる名前なので、様々な型でこの関数を見るでしょう。
次に進みましょう。
fn main() { io::stdin().read_line(&mut guess) .expect("Failed to read line"); }io::stdin().read_line(&mut guess) .expect("Failed to read line");
いろいろ出てきました。少しずつ確認していきましょう。最初の行は2つの部分で構成されます。 これが最初の部分です。
fn main() { io::stdin() }io::stdin()
プログラムの最初の行でどのように std::io
を use
したか覚えていますか?
ここでは、その関連関数を呼び出しているのです。
もし use std::io
としなかったなら、 std::io::stdin()
と書くことになります。
この関数はターミナルの標準入力へのハンドルを返します。詳しくは std::io::Stdin を見てください。
次の部分では、ハンドルを使ってユーザからの入力を取得します。
fn main() { .read_line(&mut guess) }.read_line(&mut guess)
ここで、ハンドルに対してread_line()
メソッドを呼んでいます。
メソッドは関連関数のようなものですが、型自体ではなくインスタンスに対してだけ使えます。
read_line()
に引数を1つ渡してます。 &mut guess
です。
guess
がどのように束縛されたか覚えてますか? ミュータブルであると言いました。
しかしながら、 read_line
は String
を引数に取りません。 &mut String
を取るのです。
Rustには参照と呼ばれる機能があり、1つのデータに対して複数の参照を持つことができます。
これにより、値をコピーする機会を減らせます。
Rustの主要な売りの1つが、参照をいかに安全に簡単に使えるかなので、参照は複雑な機能です。
しかしこのプログラムを作り終えるのに、今すぐ詳細を知る必要はありません。
今のところ let
と同じように、参照はデフォルトでイミュータブルであるということだけ覚えておいてください。
なので &guess
ではなく &mut guess
と書く必要があるのです。
なぜ read_line()
は文字列へのミュータブルな参照を取るのでしょうか?
read_line()
はユーザが標準入力に打ったものを取得し、それを文字列に格納します。
ですから、格納先の文字列を引数として受け取り、そこに入力文字列を追加するために、ミュータブルであることが求められるのです。
しかし、この行はまだ終わっていません。テキスト上では1行ですが、コードの論理行の1部でしかないのです。
fn main() { .expect("Failed to read line"); }.expect("Failed to read line");
メソッドを .foo()
構文で呼び出す時、改行してスペースを入れても構いません。
そうすることで長い行を分割できます。
こうすることだって できました
io::stdin().read_line(&mut guess).expect("Failed to read line");
ですがこれだと読み辛いです。そこで2つのメソッド呼び出しを、2つの行に分割したわけです。
さて read_line()
については話しましたが、 expect()
は何でしょうか?
実は、read_line()
は引数として渡した &mut String
にユーザの入力を入れるだけでなく、io::Result
という値も返すのです。
標準ライブラリには Result
という名の付く型がいくつもあります。
まず汎用のResult
があって、さらに個々のライブラリに特殊化されたバージョンもあり、io::Result
もその1つです。
これらの Result
型の目的は、エラーハンドリング情報をエンコードすることです。
Result
型の値には、他の型と同じように、メソッドが定義されています。
今回の場合 io::Result
にexpect()
メソッドが定義されており、それは、呼び出された値が成功を表すものでなければ、与えたメッセージと共にpanic!
します。
panic!
は、メッセージを表示してプログラムをクラッシュさせます。
このメソッドを呼び出さずにいると、プログラムはコンパイルできますが、警告が出ます。
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
src/main.rs:10:5: 10:39 warning: unused result which must be used,
#[warn(unused_must_use)] on by default
src/main.rs:10 io::stdin().read_line(&mut guess);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rustは Result
値を使っていないことを警告します。警告は io::Result
が持つ特別なアノテーションに由来します。
Rustはエラーの可能性があるのに、処理していないことを教えてくれるのです。
警告を出さないためには、実際にエラー処理を書くのが正しいやり方です。
幸運にも、問題があった時にそのままクラッシュさせたいなら、expect()
が使えます。
どうにかしてエラーから回復したいなら、別のことをしないといけません。
しかしそれは、将来のプロジェクトに取っておきましょう。
最初の例も残すところあと1行です。
fn main() { println!("You guessed: {}", guess); } }println!("You guessed: {}", guess); }
これは入力を保持している文字列を表示します。 {}
はプレースホルダで、引数として guess
を渡しています。
複数の {}
があれば、複数を引数を渡すことになります。
let x = 5; let y = 10; println!("x and y: {} and {}", x, y);
簡単ですね。
いずれにせよ、一巡り終えました。これまでのものを cargo run
で実行できます。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
これでよし! 最初の部分は終わりました。キーボードからの入力を取得して、出力を返すところまでできました。
次に秘密の数を生成しましょう。Rustの標準ライブラリには乱数の機能がまだありません。
ですが、Rustチームはrand
クレートを提供しています。
「クレート」はRustのコードをパッケージ化したものです。今まで作ってきたのは、実行可能な「バイナリクレート」です。
rand
は「ライブラリクレート」で、他のプログラムから使われることを意図したコードが入っています。
外部のクレートを使う時にこそ、Cargoが活きてきます。 rand
を使う前に Cargo.toml
を修正する必要があります。
Cargo.toml
を開いて、その末尾に以下の行を追加しましょう。
[dependencies]
rand="0.3.0"
Cargo.toml
の [dependencies]
(依存)セクションは [package]
セクションに似ています。
後続の行は、次のセクションが始まるまでそのセクションに属します。
Cargoはどの外部クレートのどのバージョンに依存するのかの情報を取得するのに、dependenciesセクションを使います。
今回のケースではバージョン0.3.0
を指定していますが、Cargoは指定されたバージョンと互換性のあるバージョンだと解釈します。
Cargoはバージョン記述の標準、セマンティックバージョニングを理解します。
上記のように、単にバージョンを書くのは、実は ^0.3.0
の略記になっており、「0.3.0と互換性のあるもの」という意味になります。
もし正確に 0.3.0
だけを使いたいなら rand="=0.3.0"
(等号が2つあることに注意してください)と書きます。
さらに最新版を使いたいなら *
を使います。また、バージョンの範囲を使うこともできます。
Cargoのドキュメントに、さらなる詳細があります。
さて、コードは変更せずにプロジェクトをビルドしてみましょう。
$ cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.3.8
Downloading libc v0.1.6
Compiling libc v0.1.6
Compiling rand v0.3.8
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
(もちろん、別のバージョンが表示される可能性もあります)
いろいろと新しい出力がありました。 外部依存ができたので、Cargoはそれぞれの最新版についての情報を、レジストリという、Crates.ioからコピーしたデータから取得します。 Crates.ioは、Rustのエコシステムに参加している人たちが、オープンソースのRustプロジェクトを投稿し、共有するための場所です。
レジストリをアップデートした後に、Cargoは [dependencies]
を確認し、まだ手元にないものがあればダウンロードします。
今回のケースでは rand
に依存するとだけ書きましたが、 libc
も取得されています。これは rand
が動作するのに libc
に依存するためです。
ダウンロードが終わったら、それらをコンパイルし、続いてプロジェクトをコンパイルします。
もう一度 cargo build
を走らせると、異なった出力になります。
$ cargo build
そうです、何も出力されないのです。
Cargoはプロジェクトがビルドされていて、依存もビルドされていることを知っているので、それらを繰り返さないのです。
何もすることがなければそのまま終了します。もし src/main.rs
を少し変更して保存したら、次のように表示されます。
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Cargoには rand
の 0.3.x
を使うと伝えたので、執筆時点の最新版 v0.3.8
を取得しました。
ですがもし来週 v0.3.9
が出て、重要なバグがフィクスされたらどうなるのでしょう?
バグフィクスを取り込むのは重要ですが、 0.3.9
にコードが動かなくなるようなリグレッションがあったらどうしましょう?
この問題への答えは、プロジェクトのディレクトリにある Cargo.lock
です。
プロジェクトを最初にビルドした時に、Cargoは基準を満たす全てのバージョンを探索し、 Cargo.lock
ファイルに書き出します。
その後のビルドでは、Cargoはまず Cargo.lock
ファイルがあるか確認し、再度バージョンを探索することなく、そこで指定されたバージョンを使います。
これで自動的に再現性のあるビルドが手に入ります。
言い換えると、明示的にアップグレードしない限り、私たちは 0.3.8
を使い続けますし、ロックファイルのおかげで、コードを共有する人たちも 0.3.8
を使い続けます。
では v0.3.9
を 使いたい 時はどうすればいいのでしょうか?
Cargoには別のコマンド update
があり、次のことを意味します:「ロックを無視して、指定したバージョンを満たす全ての最新版を探しなさい。それに成功したら、ロックファイルに書きなさい」
しかし、デフォルトではCargoは 0.3.0
より大きく、 0.4.0
より小さいバージョンを探しにいきます。
0.4.x
より大きなバージョンを使いたいなら直接 Cargo.toml
を更新する必要があります。
そうしたら、次に cargo build
をする時に、Cargoはインデックスをアップデートして、rand
の要件を再評価します。
Cargoとそのエコシステムについては、説明することがまだ色々あるのですが、今のところは、これらのことだけを知っておけば十分です。 Cargoのおかげでライブラリの再利用は本当に簡単になりますし、Rustaceanは他のパッケージをいくつも使った小さなライブラリをよく書きます。
rand
を実際に 使う ところに進みましょう。次のステップはこれです。
extern crate rand; use std::io; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); 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); }
訳注:
- The secret number is: {}: 秘密の数字は: {}です
1つ目の変更は最初の行です。 extern crate rand
としました。
rand
を [dependencies]
に宣言したので、 extern crate
でそれを使うことをRustに伝えています。
これはまた、 use rand;
と同じこともしますので、 rand
にあるものは rand::
と前置すれば使えるようになります。
次にもう1行 use
を追加しました。 use rand::Rng
です。
この後すぐ、あるメソッドを使うのですが、それが動作するには Rng
をスコープに入れる必要があるのです。
基本的な考え方は次の通りです。このメソッドは「トレイト」と呼ばれるもので定義されており、動作させるために、該当するトレイトをスコープに入れる必要があるのです。
詳しくはトレイトセクションを読んでください。
中ほどにもう2行足してあります。
fn main() { let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); }let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number);
rand::thread_rng()
を使って、いま現在の実行スレッドに対してローカルな、乱数生成器のコピーを取得しています。
上で use rand::Rng
したので、生成器は gen_range()
メソッドを使えます。
このメソッドは2つの引数を取り、その間の数を1つ生成します。
下限は含みますが、上限は含まないので、1から100までの数を生成するには 1
と 101
を渡す必要があります。
2行目は秘密の数字を表示します。 これは開発する時には有用で、簡単に動作確認できます。 もちろん最終版では削除します。 最初に答えを見せたら、ゲームじゃなくなってしまいます!
更新したプログラムを、何度か実行してみましょう。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4
$ cargo run
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 83
Please input your guess.
5
You guessed: 5
うまくいきました。次は予想値と秘密の数を比較します。
ユーザーの入力を受け取れるようになったので、秘密の数と比較しましょう。 まだコンパイルできませんが、これが次のステップです。
extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); 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); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); 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); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
訳注:
- Too small!: 小さすぎます!
- Too big!: 大きすぎます!
- You win!: あなたの勝ちです!
いくつか新しいことがあります。まず use
が増えました。
std::cmp::Ordering
という型をスコープに導入しています。
また、それを使うためのコードを末尾に5行追加しました。
match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), }
cmp()
は比較可能な全てのものに対して呼べるメソッドで、引数として、比較したい相手の参照を取ります。
そして、先ほど use
した、Ordering
型の値を返します。
match
文を使って、正確に Ordering
のどれであるかを判断しています。
Ordering
は enum
(列挙型) で、enumは「enumeration(列挙)」の略です。
このようなものです。
enum Foo { Bar, Baz, }
この定義だと、 Foo
型のものは Foo::Bar
あるいは Foo::Baz
のいずれかです。
::
を使って enum
のバリアントの名前空間を指定します。
Ordering
enum
は3つのバリアントを持ちます。 Less
、 Equal
、 Greater
です。
match
文ではある型の値を取って、それぞれの可能な値に対する「腕」を作れます。
Ordering
には3種類あるので、3つの腕を作っています。
match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), }
Less
なら Too small!
を、 Greater
なら Too big!
を、 Equal
なら You win!
を表示します。
match
はとても便利で、Rustでよく使われます。
これはコンパイルが通らないと言いました。試してみましょう。
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
src/main.rs:28:21: 28:35 error: mismatched types:
expected `&collections::string::String`,
found `&_`
(expected struct `collections::string::String`,
found integral variable) [E0308]
src/main.rs:28 match guess.cmp(&secret_number) {
^~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `guessing_game`.
うわ、大きなエラーです。核心になっているのは「型の不一致」です。
Rustには強い静的な型システムがあり、また、型推論もあります。
let guess = String::new()
と書いた時、Rustは guess
が文字列であるはずだと推論できるので、わざわざ型を書かなくてもよいのです。
また、secret_number
では、1から100までの数値を表せる型として、いくつかの候補があり、例えば、32bit数の i32
、符号なし32bit数の u32
、64bit数の i64
などが該当します。
これまで、そのどれであっても良かったため、Rustはデフォルトの i32
としてました。
しかしここで、Rustは guess
と secret_number
の比較のしかたが分からないのです。
これらは同じ型である必要があります。
ということは、私たちは本当は、入力として読み取った String
を、比較のために実数の型にしたかったわけです。
それは2行追加すればできます。
新しいプログラムです。
extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); 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"); 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!"), } }
新しい2行はこれです。
fn main() { let guess: u32 = guess.trim().parse() .expect("Please type a number!"); }let guess: u32 = guess.trim().parse() .expect("Please type a number!");
ちょっと待ってください、既に guess
を定義してありますよね?
たしかにそうですが、Rustでは以前の guess
の定義を新しいもので「覆い隠す」ことができるのです(訳注: このように隠すことをシャドーイングといいます)。
まさにこのように、最初 String
であった guess
を u32
に変換したい、というような状況でよく使われます。
シャドーイングのおかげで guess_str
と guess
のように別々の名前を考える必要はなくなり、 guess
の名前を再利用できます。
guess
を先に書いたような値に束縛します。
guess.trim().parse()
ここでの guess
は、古い guess
を指しており、入力を保持する String
です。
String
の trim()
メソッドは、文字列の最初と最後にある空白を取り除きます。
read_line()
を満たすには「リターン」キーを押す必要があるので、これは重要です。
つまり 5
と入力してリターンを押したら、 guess
は 5\n
のようになっています。
\n
は「改行」、つまり、エンターキーを表しています。
trim()
することで、5
だけを残してこれを取り除けます。
文字列の parse()
メソッドは、文字列を何かの数値へとパースします。
様々な数値をパースできるので、Rustに正確にどの型の数値が欲しいのかを伝える必要があります。
なので let guess: u32
と書いたのです。
guess
の後のコロン(:
)は型注釈を付けようとしていることをRustに伝えます。
u32
は符号なし32bit整数です。
Rustには 様々なビルトインの数値型 がありますが、今回は u32
を選びました。
小さな正整数にはちょうどいいデフォルトとなる選択肢です。
read_line()
と同じように、 parse()
の呼び出しでもエラーが起こり得ます。
文字列に A👍%
が含まれていたらどうなるでしょう? それは数値には変換できません。
ですから read_line()
と同じように expect()
を使って、エラーがあったらクラッシュするようにします。
プログラムを試してみましょう。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
ばっちりです。予想値の前にスペースも入れてみましたが、それでも私が76と予想したんだと、ちゃんと理解してくれました。 何度か動かしてみて、当たりが動くこと、小さい数字も動くことを確認してみてください。
ゲームが完成に近づいてきましたが、まだ、1回しか予想できません。 ループを使って書き換えましょう。
loop
キーワードで無限ループが得られます。追加しましょう。
extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); 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 = 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!"), } } }
試してみましょう。え?でも待ってください、無限ループを追加しましたよね。
そうです。でも parse()
に関する議論を覚えてますか? 数字でない答えを入力すると panic!
して終了するのでした。
見ててください。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/guessing_game`
Guess the number!
The secret number is: 59
Please input your guess.
45
You guessed: 45
Too small!
Please input your guess.
60
You guessed: 60
Too big!
Please input your guess.
59
You guessed: 59
You win!
Please input your guess.
quit
thread '<main>' panicked at 'Please type a number!'
はいこの通り、たしかに quit
で終了しました。他の数字でないものを入れても同じです。
でもこれは、お世辞にも良いやり方とは言えませんね。
まず、ゲームに勝ったら本当に終了するようにしましょう。
extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); 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 = 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!"); break; } } } }
You win!
の後に break
を加えることで、ゲームに勝った時にループを抜けます。
ループを抜けることは同時に、それが main()
の最後の要素なので、プログラムが終了することも意味します。
もう1つ修正します。数値でない入力をした時に終了するのではなく、無視させましょう。
それはこのようにできます。
extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); 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; } } } }
変更はこれです。
fn main() { let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; }let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };
このように、「エラーならクラッシュ」から「実際に戻り値のエラーをハンドルすること」へ移行する一般的な方法は、expect()
を match
文に変更することです。
parse()
は Result
を返します。
これは Ordering
と同じような enum
ですが、今回はそれぞれのバリアントにデータが関連付いています。
Ok
は成功で、 Err
は失敗です。それぞれには追加の情報もあります。パースに成功した整数、あるいはエラーの種類です。
このケースでは Ok(num)
に対して match
していて、これは Ok
をアンラップして得られた値(整数値)を num
という名前に設定します。続く右側では、その値をそのまま返しています。
Err
の場合、エラーの種類は気にしにないので、名前ではなく、任意の値にマッチする _
を使いました。
こうすれば Ok
以外の全てをキャッチすることができ、continue
によって、loop の次の繰り返しに進みます。
こうして全てのエラーを無視し、プログラムの実行を続けることが可能になるのです。
これでいいはずです。試してみましょう。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/guessing_game`
Guess the number!
The secret number is: 61
Please input your guess.
10
You guessed: 10
Too small!
Please input your guess.
99
You guessed: 99
Too big!
Please input your guess.
foo
Please input your guess.
61
You guessed: 61
You win!
素晴らしい! 最後にほんの少し修正して、数当てゲームの制作を終えましょう。 なんだか分かりますか? そうです、秘密の数字は表示したくありません。 テストには便利でしたが、ゲームを台無しにしてしまいます。 これが最終的なソースコードです。
extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); 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; } } } }extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); 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; } } } }
数当てゲームが遂に完成しました! お疲れ様でした!
このプロジェクトでは、様々なものをお見せしました。
let
、 match
、メソッド、関連関数、外部クレートの使い方、などなど。
次の章では、それぞれについて、さらに深く学んでいきましょう。