数当てゲーム

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!"); }
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 runcargo 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); }
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() {
fn main() {

すでに見てきたように main() 関数がプログラムのエントリポイントになります。 fn 構文は新たな関数を宣言し、 () で引数がないことを示し、 { が関数本体の始まりです。 返り値の型は書かなかったので、 () 、つまり空のタプルとして扱われます。

fn main() { println!("Guess the number!"); println!("Please input your guess."); }
    println!("Guess the number!");

    println!("Please input your guess.");

前に println!()文字列をスクリーンに表示するマクロであることを学びました。

fn main() { let mut guess = String::new(); }
    let mut guess = String::new();

少し興味深いものが出てきました。このわずか1行で、様々なことが起こっています。 最初に気付くのは、これが「変数束縛」を作るlet文であることです。 let文は以下の形を取ります。

fn main() { let foo = bar; }
let foo = bar;

これは foo という名前の束縛を作り、それを値 bar に束縛します。 多くの言語ではこれを「変数」と呼びますが、Rustの変数束縛は少しばかり皮を被せてあります。

例えば、束縛はデフォルトでイミュータブル (不変)です。 ですから、この例ではイミュータブルではなく、ミュータブル(可変)な束縛にするために mut を使っているのです。 let は代入の左辺に単に1つの名前を取るのではなく、実際にはパターンを受け取ります。 パターンは後ほど使います。今のところ、すごく簡単ですね。

fn main() { // let foo = 5; // immutable. let foo = 5; // イミュータブル // let mut bar = 5; // mutable let mut bar = 5; // ミュータブル }
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::iouse したか覚えていますか? ここでは、その関連関数を呼び出しているのです。 もし 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_lineString を引数に取りません。 &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() 構文で呼び出す時、改行してスペースを入れても構いません。 そうすることで長い行を分割できます。 こうすることだって できました

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つのメソッド呼び出しを、2つの行に分割したわけです。 さて read_line() については話しましたが、 expect() は何でしょうか? 実は、read_line() は引数として渡した &mut String にユーザの入力を入れるだけでなく、io::Result という値も返すのです。 標準ライブラリには Result という名の付く型がいくつもあります。 まず汎用のResultがあって、さらに個々のライブラリに特殊化されたバージョンもあり、io::Result もその1つです。

これらの Result 型の目的は、エラーハンドリング情報をエンコードすることです。 Result 型の値には、他の型と同じように、メソッドが定義されています。 今回の場合 io::Resultexpect()メソッドが定義されており、それは、呼び出された値が成功を表すものでなければ、与えたメッセージと共に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 を渡しています。 複数の {} があれば、複数を引数を渡すことになります。

fn main() { let x = 5; let y = 10; println!("x and y: {} and {}", x, y); }
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には rand0.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); }
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までの数を生成するには 1101 を渡す必要があります。

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行追加しました。

fn main() { match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
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 のどれであるかを判断しています。 Orderingenum (列挙型) で、enumは「enumeration(列挙)」の略です。 このようなものです。

fn main() { enum Foo { Bar, Baz, } }
enum Foo {
    Bar,
    Baz,
}

この定義だと、 Foo 型のものは Foo::Bar あるいは Foo::Baz のいずれかです。 :: を使って enum のバリアントの名前空間を指定します。

Ordering enum は3つのバリアントを持ちます。 LessEqualGreater です。 match 文ではある型の値を取って、それぞれの可能な値に対する「腕」を作れます。 Ordering には3種類あるので、3つの腕を作っています。

fn main() { match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
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は guesssecret_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!"), } }
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 であった guessu32 に変換したい、というような状況でよく使われます。 シャドーイングのおかげで guess_strguess のように別々の名前を考える必要はなくなり、 guess の名前を再利用できます。

guess を先に書いたような値に束縛します。

fn main() { guess.trim().parse() }
guess.trim().parse()

ここでの guess は、古い guess を指しており、入力を保持する String です。 Stringtrim() メソッドは、文字列の最初と最後にある空白を取り除きます。 read_line() を満たすには「リターン」キーを押す必要があるので、これは重要です。 つまり 5 と入力してリターンを押したら、 guess5\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!"), } } }
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; } } } }
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; } } } }
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;
            }
        }
    }
}

終わりに

数当てゲームが遂に完成しました! お疲れ様でした!

このプロジェクトでは、様々なものをお見せしました。 letmatch 、メソッド、関連関数、外部クレートの使い方、などなど。 次の章では、それぞれについて、さらに深く学んでいきましょう。