注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
最初のプロジェクトとして、古典的な初心者向けのプログラミングの問題、数当てゲームを実装します。 動作: プログラムは1から100の間のあるランダムな数字を生成します。 そしてその値の予想値の入力を促します。 予想値を入力すると大きすぎるあるいは小さすぎると教えてくれます。 当たったらおめでとうと言ってくれます。良さそうですか?
新しいプロジェクトを作りましょう。プロジェクトのディレクトリへ行って下さい。
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のコマンドを紹介させて下さい。 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
は代入の左辺に名前を取る訳ではなくて実際にはパターンを受け取ります。
後程パターンを使います。簡単なのでもう使えますね。
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");
ですがこれだと読み辛いです。ですので3つのメソッド呼び出しを3行に分割します。
read_line()
については話しましたが expect
についてはどうでしょう?
さて、 read_line()
がユーザの入力を &mut String
に入れることには言及しました。
しかし値も返します。
この場合、標準ライブラリにある汎用のResult
であり、そしてそれをサブライブラリに特殊化したバージョンの io::Result
になります。
これらの Result
型の目的は、エラーハンドリング情報をエンコードすることです。
Result
型の値には、他の型と同じように、メソッドが定義されています。
今回は io::Result
に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行です。
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: {}: 秘密の数字は: {}です
です。
まず最初に変更したのは最初の行です。 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から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()
は比較可能なものに対しならなんでも呼べて、引数に比較したい対象の参照を取ります。
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不動小数点数 f64
あるいはそれ以外、様々な型がありえます。
これまで、それは問題ではありませんでしたので、Rustは i32
をデフォルトとしてました。
しかしながらここで、 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) .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!"), } }
新しい3行はこれです。
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
の guess
です。
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, };
これが「エラーならクラッシュ」から「実際に返値のエラーをハンドルする」への一般的な移行の仕方です。
parse()
の返す値は Ordering
と同じような enum
ですが、今回はそれぞれのバリアントにデータが関連付いています。
Ok
は成功で、 Err
は失敗です。それぞれには追加の情報もあります。パースに成功した整数、あるいはエラーの種類です。
このケースでは、 Ok(num)
に対して match
していて、それで Ok
に内包された値を num
という名前に設定しており、右側でそのまま返しています。
Err
の場合、エラーの種類は気にしにないので、名前ではなく _
を使います。
これはエラーを無視していて、 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
、メソッド、関連関数、外部クレートの使い方、などなど。
次のプロジェクトではさらに色々見せます。