Rustドキュメントの日本語訳は Rustドキュメント翻訳プロジェクト に移行しました。
http://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/guessing-game.html にてRust 1.6原文に追従した版があります。
Rust 1.0時点の Learn Rust - Guessing Game より訳出。
- 元ソース:https://github.com/rust-lang/rust/blob/1.0.0/src/doc/trpl/guessing-game.md
- ライセンス:MIT licenseまたはApache License 2.0, https://github.com/rust-lang/rust/blob/1.0.0/COPYRIGHT
訳者注:Qiita Markdownソースには原文をコメント形式<!--
/-->
で残してあります。誤訳修正や訳出改善の編集リクエストを歓迎します。
数字当てゲーム(Guessing Game)
私たちの最初のプロジェクトとして、プログラミング入門の古典問題:数当てゲームを実装していきます。動作はこんな感じです:プログラムは1から100までの間でランダムな整数を一つ生成し、ユーザが推測した値を入力させます。推測値が入力されると、プログラムが選んだ数字よりも高いか低いかを教えます。正しい値に到達したら、そこでゲームクリアです。いい感じでしょ?
セットアップ(Set up)
では新しいプロジェクトを準備しましょう。プロジェクト用のディレクトリに行ってください。ディレクトリ構造や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.0.1"
authors = ["Your Name <you@example.com>"]
Cargoはこの情報をあなたの環境から取得しています。間違っていたら直しておいてください。
最後に、Cargoは‘Hello, world!’を生成してくれます。src/main.rs
を確認してみます:
fn main() {
println!("Hello, world!")
}
さっそくCargoが用意したものをコンパイルしましょう:
$ cargo build
Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
よろしい!ではもう一度src/main.rs
を開いてください。私たちのコードは全部このファイルに書いていきます。
先に進む前に、もう一つCargoのコマンドrun
を説明しておきましょうか。cargo run
はcargo build
と似ていますが、出来上がったファイルの実行まで行います。では試してみます:
$ cargo run
Compiling guessing_game v0.0.1 (file:///home/you/projects/guessing_game)
Running `target/debug/guessing_game`
Hello, world!
素晴らしい!プロジェクトをすばやくイテレーションする場合は、run
コマンドが便利です。私たちのゲームは、次ステップへ進む前にはイテレーション毎で確認する必要があるため、まさにその手のプロジェクトといえます。
推測値を処理する(Processing a Guess)
では始めましょう!数当てゲームで最初にすべきことは、プレイヤーに推測値を入力させることです。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)
.ok()
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
盛りだくさんですね!少しづつ見ていきましょうか。
use std::io;
私たちはユーザ入力を受け付けて、さらに出力として結果を表示しなければなりません。ということで、標準ライブラリにあるio
ライブラリが必要になります。Rustでは、すべてのプログラムに「プレリュード(prelude)」に含まれるものしかインポートしません。あるライブラリ機能がプレリュードに含まれないときは、直接use
する必要があります。
fn main() {
すでに見たとおり、main()
関数がプログラムのエントリポイントになります。fn
構文は新しい関数を宣言し、()
は引数が無いことを表します。そして{
で関数の本体が開始します。戻り値型を明記しなかったため、ここでは空のタプル(tuple)()
とみなされます。
println!("Guess the number!");
println!("Please input your guess.");
println!()
が画面に文字列(string)を表示するマクロ(macro)であることは前に学びました。
let mut guess = String::new();
さあ面白いところにきました!この1行には様々な要素がふくまれています。最初に気づくのは、これが「変数束縛(variable bindings)」に用いられるlet文(let statement)であることです。次の形式をとります:
let foo = bar;
これはfoo
という名前で新しい束縛を作り、そこにbar
の値を結びつけます(bind)。多くのプログラミング言語では「変数(variable)」と呼ばれますが、Rustの変数束縛にはいくつかの秘策が隠されているのです。
例えば、変数束縛はデフォルトでimmutableです。私たちの例でmut
を使うのはこのため: immutableではなく、mutableな束縛とするためです。let
はその左側に名前をとるのではなく、実は「パターン(pattern)」を書くことができます。パターンは後ほど使いましょう。今は簡単な使い方で十分です:
let foo = 5; // immutable.
let mut bar = 5; // mutable
あと、//
から行末まではコメントになります。Rustはコメント(comments)部分を全部無視します。
let mut guess
が名前guess
のmutable束縛を導入すると理解したところで、=
の逆側にあるString::new()
が意味するところを見ていく必要があります。
String
は文字列型であり、標準ライブラリで提供されます。String
は可変長であり、テキストをUTF-8エンコーディングしたものです。
::new()
構文は::
を使いますが、これはある型に「関連する関数(associated function)」となっているためです1。つまり、String
型の特定のインスタンスではなく、String
型自身に関連付けられています。プログラミング言語によってはこれを「静的メソッド(static method)」と呼ぶものもあります。
この関数は新しい空のString
を生成するため、new()
という名前を持ちます。その型の新しい値を作る一般的な名前のため、多くの型でnew()
関数を見つけられるでしょう。
では次に行きましょう:
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
これまた沢山!ちょっとづつ見ていきましょう。最初の行は2つのパートから成ります。まずはこちら:
io::stdin()
プログラムの冒頭行でstd::io
をuse
したのを覚えていますか?ここでは関連する関数(associated function)を呼び出しているのです。もしuse std::io
がなかったら、この行はstd::io::stdin()
と書かなければなりませんでした。
この関数は端末の標準入力へのハンドルを返します。具体的にはstd::Stdinを返します。
続くパートでは、ユーザ入力の取得にこのハンドルを使います:
.read_line(&mut guess)
ここでは、ハンドルに対してread_line()
メソッドを呼び出します。メソッド(method)は関連する関数(associated function)と似ていますが、その型自身ではなく、型の特定インスタンスに対してのみ有効です。またread_line()
には引数も1つ: &mut guess
を渡しています。
上でguess
に束縛したやり方を覚えていますか?mutableな束縛としましたね。しかし、read_line
は引数としてString
を取りません: &mut String
を取ります。Rustには、あるデータへの複数個の参照ができて、コピー回数を減らせる、「参照(references)」という機能があります。参照を安全かつ簡単に扱えることはRustの主要なセールスポイントの1つですが、参照は複雑な機能です。もっとも、今回のプログラムを完成させるにはその詳細まで理解する必要はありません。今知っておくべきは、let
束縛と同じく参照もデフォルトでimmutableとなる点です。なので、&guess
ではなく&mut guess
と書く必要があります。
どうしてread_line()
が文字列へのmutable参照をとるのでしょう?この関数の仕事はユーザが標準入力にタイプしたものを取得し、文字列へと変換することです。なので関数は文字列を引数として取り、入力を追加するためにはmutableとする必要があるのです。
ただし、コードのこの行はまだ終わっていません。テキストの1行ではありますが、コードの1論理行の最初のパートにすぎません:
.ok()
.expect("Failed to read line");
.foo()
構文でメソッドを呼び出すとき、新しい行や空白から始めることがあります。これは長い行を分割するのに役立ちます。次のようにも書くことも_できます_:
io::stdin().read_line(&mut guess).ok().expect("failed to read line");
これでは読むのが大変です。それで3つのメソッド呼び出しを3行に分けたのです。すでにread_line()
には言及しましたが、ok()
とexpect()
はどうでしょう?ええと、read_line()
はユーザ入力を渡した&mut String
へ代入することは説明した通りです。しかしその戻り値: この場合はio::Result
も返しているのです。Rust標準ライブラリにはResult
という名前の型がいくつかあります: ジェネリックなResult
と、io::Result
のようなサブラリブラリ用の具体的な型です。
これらResult
型の目的は、エラーハンドリング情報をエンコードすることです。Result
型の値は、任意の型と同様に、メソッドを定義しています。この場合はio::Result
がok()
メソッドを持ち、その意味は「我々はこの値が成功を意味すると仮定する。そうでなければエラー情報を捨てればよい。」となります。なぜ捨ててしまうのでしょう?基本的なプログラムでは、なんらかの問題が生じたときは処理続行不可能を意味するため、一般エラーを表示できれば十分でしょう。ok()
メソッドは、もう一つのメソッドexpect()
を定義する値を返します。expect()
メソッド呼び出しには値を取り、もし成功を意味していなければ、その値はpanic!
メッセージとして渡されます。このpanic!
はプログラムをクラッシュさせ、メッセージを表示します。
これら2つのメソッド呼び出しをやめても、プログラムはコンパイルできますが、警告がなされるでしょう:
$ 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は、エラー発生の可能性を扱っていないケースを教えてくれるのです。エラーを抑制する正しい方法は、エラーハンドリング処理を実際に書くことです。幸運にも、問題が起きた場合はクラッシュさせたいだけですから、今回は2つの小さなメソッド利用で十分です。エラーからどうにかして回復できるのならば、何かしらの対処が必要ですが、それは将来のプロジェクトのためにとっておきましょう。
この最初の例では、あと1行を残すのみとなりました:
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
オーライ!最初のパートが完了です: キーボードからの入力を取得し、それを表示し返すことができました。
秘密の数字を生成する(Generating a secret number)
次は、秘密の数字を生成する必要があります。Rust標準ライブラリはまだ乱数生成機能を含んでいません。ただし、Rustチームがrand
クレートを提供しています。「クレート(crate)」はRustコードのパッケージです。私たちは実行可能な「バイナリ・クレート(binary crate)」をビルドしてきました。rand
は「ライブラリ・クレート(library crate)」であり、他プログラムから利用されることを目的としたコードを含みます。
外部クレートの利用ではCargoの真価が発揮されます。rand
を利用したコードを書く前に、私たちのCargo.toml
を変更する必要があります。ファイルを開いて、次の数行を下部に追記してください:
[dependencies]
rand="0.3.0"
Cargo.toml
の[dependencies]
セクションは[package]
と似ています: 次のセクションが始まるまでの後続内容は該当セクションに属します。Cargoは依存(dependencies)セクションを、依存先の外部クレートと要求バージョンの指定に利用します。ここでは、私たちはバージョン0.3.0
を利用します。Cargoはバージョン番号付与の標準セマンティック・バージョニングを理解します。最新版を使いたいなら、*
やバージョン範囲の指定が可能です。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)
(当然ですが、違うバージョンになるかもしれません。)
新しい出力がいっぱい!今回は外部の依存を指定したため、Crates.ioよりコピーされるレジストリから、Cargoが全ての最新バージョンを取ってきます。
レジストリを更新したのち、Cargoは[dependencies]
をチェックして足りないものをダウンロードします。ここではrand
への依存のみを記述しましたが、libc
のコピーも取得しています。これはrand
がlibc
に依存しているためです。ダウンロードが終わると、それらをコンパイルし、その後に私たちのプロジェクトをコンパイルします。
もう一度cargo build
を実行すると、異なる出力が得られるでしょう:
$ cargo build
そうです、出力なし!プロジェクトがビルド済みで、また全ての依存先もビルド済みであり、何もしなくて良いことをCargoは知っています。することがなければ、単に終了します。再びsrc/main.rs
を開いて、つまらない変更を行って、もう一度保存すれば、次の1行が出力されるでしょう:
$ 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
のままです。誰かとコードを共有する場合には、lockファイルの恩恵があるでしょう。
v0.3.9
を使い_たい_場合はどうすれば?Cargoにはもう一つのコマンドupdate
があり、その意味は「lockファイルを無視して、基準に合う最新バージョンを解決せよ。上手くいったら、それらのバージョン情報をlockファイルに書き出せ。」となります。ただし、デフォルトでは、Cargoはバージョン0.3.0
以上かつ0.4.0
未満だけを検索します。0.4.x
に移行したければ、直接Cargo.toml
を更新しなければなりません。そうすれば、次回cargo build
したときに、Cargoはインデックスを更新してrand
の要件を再評価するでしょう。
Cargoとそのエコシステムについては、もっと言うべきことがあります。とはいえ今は、ここまでが知っておくべき全てです。Cargoによってライブラリ再利用が本当に簡単になるため、Rust使い(Rustaceans)は多くのサブパッケージで組み上げられる小さなプロジェクトを書く傾向があります。
それでは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)
.ok()
.expect("failed to read line");
println!("You guessed: {}", guess);
}
最初に行ったのは冒頭行の変更です。今はextern crate rand
になっています。[dependencies]
でrand
を宣言しておいたので、Rustにその利用を教えるためにextern crate
とできます。これはuse rand;
とも同じ意味であり、rand::
プレフィクスを付ければrand
クレートの要素を使えるようになります。
続いて、もう一つのuse
行: use rand::Rng
を追加しました。すぐにメソッドを使う予定なので、そのとき要求されるRng
をスコープに入れておきましょう。基本的な考え方は次の通りです: 「トレイト(traits)」と呼ばれる何かに対してメソッドが定義され、そのメソッドを動かすには、トレイトがスコープ内に存在している必要があります。より詳細を知りたければ、トレイト(traits)の節を読んでください。
間には、2つの行を追加しました:
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
実行中のスレッド(thread)に対してローカルな、乱数生成器のコピーを得るためにrand::thread_rng()
関数を使います。上でuse rand::Rng
としたため、gen_range()
メソッドが有効になっています。このメソッドは2つの引数をとり、それらの間の数字を生成します。下限値は含みます(inclusive)が、上限値は範囲に含まれない(exclusive)ため、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
素晴らしい!次は: 推測値と秘密の数字を比較しましょう。
推測値を比較する(Comparing guesses)
ここではユーザの入力を取得して、ランダムな数字と推測値を比較しましょう。まだ上手く動きませんが、下記が次ステップです:
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)
.ok()
.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!"),
}
}
ここで新しい箇所は少しだけです。まずは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
型を返します。ここではOrdering
の種類を判定するのにmatch
文を使います。Ordering
はenum
型です。これは'enumeration(列挙)'の短縮表記で、次のようなものです:
enum Foo {
Bar,
Baz,
}
この定義では、Foo
型はFoo::Bar
またはFoo::Baz
のみを値として持ちます。::
は、固有のenum
変数の名前空間を指定するのに使います。
Ordering
列挙型(enum)では3つの値: Less
, Equal
, Greater
を取りえます。match
文は型の値をとり、それぞれの候補値から'arm(アーム; 腕)'を伸ばしていきます。Ordering
は3種類の値をもつため、3つのarmを書きます2:
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`.
ヒェー!大きな間違いがあったようです。このでの問題は‘mismatched types(型の不整合)’です。Rustは強い静的な型システムを持ちます。一方で型推論も持っています。let guess = String::new()
と書いたとき、Rustはguess
がString
型であると推論できるため、私たちは型を明示せずに済みました。そしてsecret_number
の候補には、1から100の間の整数値を保持できる型: 32ビットの数値型i32
、符号無し32ビット数値型u32
、64ビットの数値型i64
などがあります。これまでは気にしていませんでしたが、Rustはi32
をデフォルトとします。しかし、ここでは、Rustはguess
とsecret_number
を比較する方法が分かりません。同じ型同士で比べる必要があります。つまり、比較のためには入力から読み取ったString
を本当の数値型に変換したいのです。これは3行の追加で可能です。新しいプログラムは次の通り:
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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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!"),
}
}
新しいのは次の3行:
let guess: u32 = guess.trim().parse()
.ok()
.expect("Please type a number!");
ちょっと待って、もうguess
変数は使っていましたよね?その通り、ただRustでは前のguess
を新しい変数で'shadow(覆い隠す)'ことができます。最初guess
がString
だったものを、u32
に変換したいというような、まさにこの手の状況で良く用いられます。Shadowing(覆い隠し)によって名前guess
が再利用でき、guess_str
とguess
といった2つの名前をいちいち付けなくて済みます。
guess
を以前も書いたような式に束縛します:
guess.trim().parse()
さらにok().expect()
の呼び出しが続きます。ここで、guess
はString
型で入力を保持している古い方のguess
を指しています。String
のtrim()
メソッドは、文字列前/後にある空白文字を全て取り除きます。read_line()
では必ずreturn
キーを入力させるため、この処理は重要です。つまり5
とタイプしてからreturnを入力すると、guess
は5\n
のようになっているのです。ここで\n
はエンターキーによる'newline'を表します。trim()
でこれを取り除き、文字列を5
だけにするのです。文字列型のparse()
メソッドは、文字列を数値へと解析(parse)します。解析される数値型は複数候補があるため、Rustには欲しい正確な数値型を教える必要があります。よってlet guess: u32
とします。guess
直後のコロン(:
)はRustに型の注釈を与えています。u32
は符号なし(unsigned)の32bit整数型です。Rustは数種類の組込み値型を提供しますが、ここではu32
を選びました。小さな正数に対しては、良いデフォルトの選択です。
read_line()
と同様に、parse()
呼び出しもエラーの可能性があります。文字列がA👍%
を含んだらどうなるでしょう?これらは数値へと変換できません。そこで、read_line()
のときと同じ方法をとります: ok()
とexpect()
メソッドを使って、エラー時はクラッシュさせます。
ではプログラムを動かしてみましょう!
$ cargo run
Compiling guessing_game v0.0.1 (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回しか行えません。繰り返し処理を加えていきましょう!
繰り返し処理(Looping)
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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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()
のときの議論を忘れた?数値以外の答えが入力されたら、return
して終了しましょう。やってみます:
$ cargo run
Compiling guessing_game v0.0.1 (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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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)
.ok()
.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 guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
このok().expect()
からmatch
文への切り替えは、「エラー発生でクラッシュ」から「実際のエラーハンドリング処理」に移行する一般的なやり方です。parse()
により返されたResult
はOrdering
同様に列挙型(enum)なのですが、こちらはそれぞれが状況に関連したデータを保持しています: Ok
は成功に、Err
は失敗を表します。それぞれ追加の情報: 成功時には解析された数値を、失敗時はエラー型を含んでいます。ここでは、Ok(num)
にmatch
したときは、Ok
値が保持する値をnum
に取り出して、右辺にそれを置いて値を戻しています。Err
の場合には、エラーの種類を気しないため、名前の代わりに_
を使っています。これによりエラーを無視して、continue
でloop
の次イテレーションへと進ませます。
これで上手くいくはず!レッツ・トライ:
$ cargo run
Compiling guessing_game v0.0.1 (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)
.ok()
.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;
}
}
}
}
完成!(Complete!)
ようやく、数当てゲームの作成に成功しました!おめでとう!
このプロジェクトではたくさんの要素を説明しました: let
、match
、メソッド、関連する関数(associated functions)、外部クレート利用など。次のプロジェクトでも色々見ていきましょう。