Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chapter 2.0 数当てゲームをプログラムする #4

Open
euledge opened this issue Feb 7, 2019 · 9 comments
Open

Chapter 2.0 数当てゲームをプログラムする #4

euledge opened this issue Feb 7, 2019 · 9 comments

Comments

@euledge
Copy link
Owner

euledge commented Feb 7, 2019

プログラムは1から100までの乱数整数を生成します。そしてプレーヤーに予想を入力するよう促します。予想を入力したら、プログラムは、 その予想が小さすぎたか大きすぎたかを出力します。予想が当たっていれば、ゲームは祝福メッセージを表示し、 終了します。

@euledge
Copy link
Owner Author

euledge commented Feb 7, 2019

標準ライブラリを使用する

入出力を行うための標準ライブラリとして std::io を使用する

ライブラリを使用するためには、以下のようにC#言語のような記述をファイルの先頭部分に書く。

use std::io;

デフォルトで使用できる標準ライブラリとしてstd::ioのほか以下のものが用意されている

std::marker::{Copy, Send, Sized, Sync}. The marker traits indicate fundamental properties of types.
std::ops::{Drop, Fn, FnMut, FnOnce}. Various operations for both destructors and overloading ().
std::mem::drop, a convenience function for explicitly dropping a value.
std::boxed::Box, a way to allocate values on the heap.
std::borrow::ToOwned, The conversion trait that defines to_owned, the generic method for creating an owned type from a borrowed type.
std::clone::Clone, the ubiquitous trait that defines clone, the method for producing a copy of a value.
std::cmp::{PartialEq, PartialOrd, Eq, Ord }. The comparison traits, which implement the comparison operators and are often seen in trait bounds.
std::convert::{AsRef, AsMut, Into, From}. Generic conversions, used by savvy API authors to create overloaded methods.
std::default::Default, types that have default values.
std::iter::{Iterator, Extend, IntoIterator, DoubleEndedIterator, ExactSizeIterator}. Iterators of various kinds.
std::option::Option::{self, Some, None}. A type which expresses the presence or absence of a value. This type is so commonly used, its variants are also exported.
std::result::Result::{self, Ok, Err}. A type for functions that may succeed or fail. Like Option, its variants are exported as well.
std::slice::SliceConcatExt, a trait that exists for technical reasons, but shouldn't have to exist. It provides a few useful methods on slices.
std::string::{String, ToString}, heap allocated strings.
std::vec::Vec, a growable, heap-allocated vector.

@euledge
Copy link
Owner Author

euledge commented Feb 7, 2019

2.2.1 値を変数に保持する

入力した文字列を格納するための変数を以下のように定義する

let mut guess = String::new();

Rustでは変数はデフォルトで宣言すると immutable となり変更不可能な状態で作成される。

let foo = 5;
for = 3;  // ここでコンパイルエラーが発生します。

Rustでは let foo = 5 と書くことを fooを値5に束縛するという言い方をする。

宣言後に可変な値を持てるようにするには

let mut foo = 5;

のようにletの後ろにmutキーワードをつける。 mutは mutable の略

右辺の String::new() は、Javaでいうと new String() に相当するもの
ただしRustではnewは関数であり、::の記法は String型の関連関数であることを表している。
関連関数は Staticメソッドとかクラスメソッドと同じ意味

@euledge
Copy link
Owner Author

euledge commented Feb 7, 2019

標準入力からの値の読み込み

io::stdin() .read_line(&mut guess)
        .expect("failed to read line");

io::stdin() .read_line(&mut guess) は標準入力(stdin)からキーボード入力を受け付け改行までの値を関数の引数 guess に値を設定することを示す。
この値は可変(mutable)である必要がある。

&という記号は、この引数が参照であることを表し、これのおかげで、データを複数回メモリにコピーせずとも、 コードの複数箇所で同じデータにアクセスできるようになる

参照については第4章に説明があるらしい。

@euledge
Copy link
Owner Author

euledge commented Feb 7, 2019

2.2.2 Result型で失敗の可能性を扱う

read_lineメソッドは、渡された文字列にユーザが入力したものを入れ込むだけでなく、 io::Result 型の値も返します。
Result型は、列挙(enum)型であり、列挙子はOkかErrです。Ok列挙子は、処理が成功したことを表し、 中に生成された値を保持します。Err列挙子は、処理が失敗したことを意味し、Errは、処理が失敗した過程や、 理由などの情報を保有します。

io::stdin() .read_line(&mut guess)
        .expect("failed to read line");

io::Resultオブジェクトには、呼び出し可能なexpectメソッドがあります。 このio::ResultオブジェクトがErr値の場合、expectメソッドはプログラムをクラッシュさせ、 引数として渡されたメッセージを表示します。 assertみたいな感じかな?

以下のようにexpectメソッドを呼び出さなかったら、コンパイル時に警告が出る

io::stdin() .read_line(&mut guess);
warning: unused `std::result::Result` that must be used
  --> src\main.rs:9:5
   |
9  | /     io::stdin()
10 | |         .read_line(&mut guess);
   | |_______________________________^
   |
   = note: #[warn(unused_must_use)] on by default
   = note: this `Result` may be an `Err` variant, which should be handled

@euledge
Copy link
Owner Author

euledge commented Feb 7, 2019

println!マクロのプレースホルダーで値を出力する

println!("You guessed: {}", guess);

{}は、プレースホルダーで引数に指定した値をフォーマットして文字列として表示します。

@euledge
Copy link
Owner Author

euledge commented Feb 10, 2019

秘密の数字を生成する

ユーザーが当てる数字を毎回、異なる数字にするために乱数発生をする。 Rustの標準ライブラリには、乱数機能がないが、 Rustの開発チームがrandクレートを用意してくれています。
クレート (crate) はRustで作成された成果物のこと。実行可能なものもあるし、ライブラリとして他から呼び出すものもある。また公開されているクレートはリポジトリcrates.ioから取得したり自作のクレートを登録することができる。

リポジトリからクレートを取得するには Cargo.tomlの [dependencies] セクションの下に取得したいクレートを記述する。

[dependencies]
rand = "0.3.14"

この状態で cargo buildを実行するとリポジトリから dependenciesに記載したクレートを取得するとともにそのクレートが依存している別のクレートへの依存関係を解決して一緒にダウンロードを行います。

$ cargo build
    Updating crates.io index
  Downloaded rand v0.3.23
  Downloaded rand v0.4.6
  Downloaded libc v0.2.48
  Downloaded winapi v0.3.6
   Compiling winapi v0.3.6
   Compiling libc v0.2.48
   Compiling rand v0.4.6
   Compiling rand v0.3.23
   Compiling gussing_game v0.1.0 (D:\workspace\RustGuidebook\Chapter2\gussing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 52.18s

利用するクレートがダウンロードできたので、ソースにそれを使用する宣言を記述します。

extern crate rand;

use std::io;
use rand::Rng;

extern crate rand; コンパイラにrandクレートを外部依存として使用することを知らせる行を追加しています。 これにより、use randを呼ぶのと同じ効果が得られるので、randクレートのものをrand::という接頭辞をつけて呼び出せるようになりました。
って書いてあるけど試しに extern crate rand; を削除しても、普通に使えるっぽい??? 何が違うんだろう? ==> 2018 editionから不要になったとのこと
https://rust-lang-nursery.github.io/edition-guide/rust-2018/module-system/path-clarity.html

次に、別のuse行を追加しています: use rand::Rngですね。Rngトレイトは
乱数生成器が実装するメソッドをもつ Rngトレイトをスコープで呼び出してその中の関数を呼びだせるように指定します。

ちなみにこの記述を削除すると、以下のようなエラーメッセージが表示され use rand::Rng を記述することを促されます。

$ cargo run
   Compiling gussing_game v0.1.0 (D:\workspace\RustGuidebook\Chapter2\gussing_game)
error[E0599]: no method named `gen_range` found for type `rand::ThreadRng` in the current scope
 --> src\main.rs:7:44
  |
7 |     let secret_number = rand::thread_rng().gen_range(1, 101);
  |                                            ^^^^^^^^^
  |
  = help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope, perhaps add a `use` for it:
  |
1 | use rand::Rng;
  |

@euledge
Copy link
Owner Author

euledge commented Feb 10, 2019

予想と秘密の数字を比較する

Rustには、強い静的型システムがあります。 しかし、型推論にも対応しています。let guess = String::new()と書いた時、コンパイラは、 guessがString型であるはずと推論してくれ、その型を明示させられることはありませんでした。 一方で、secret_number変数は、数値型です。1から100を表すことができる数値型はいくつかあります: i32は32ビットの数字; u32は32ビットの非負数字; i64は64ビットの数字などです。 Rustでの標準は、i32型であり、型情報をどこかに追加して、コンパイラに異なる数値型だと推論させない限り、 secret_numberの型はこれになります。
エラーの原因は、Rustでは、文字列と数値型を比較できないので型変換が必要になります。

入力値を文字列として読み込んだ変数

let mut guess = String::new();

Rustでは、新しい値でguessの値を覆い隠す(shadow)ことが許されているため以下のように記述することができます。これは値を別の型に変換したいシチュエーションでよく使われます。 シャドーイング(shadowing)のおかげで別々の変数を2つ作らされることなく、guessという変数名を再利用することができます。

let guess: u32 = guess.trim().parse()
    .expect("Please type a number!");

let guess: u32として :の後ろに型を指定することでコンパイラに変数の型を注釈として与えることができます。 guess.trim().parse() はParse失敗する場合(文字列を入れるなど)もあるのでResult型を返します。.expect()を記述することでOKの場合はその値、NGの場合はpanic(例外)とすることができます。

$ cargo build
   Compiling gussing_game v0.1.0 (D:\workspace\RustGuidebook\Chapter2\gussing_game)
error[E0308]: mismatched types
  --> src\main.rs:21:21
   |
21 |     let guess:i32 = guess.trim().parse();
   |                     ^^^^^^^^^^^^^^^^^^^^ expected i32, found enum `std::result::Result`
   |
   = note: expected type `i32`
              found type `std::result::Result<_, _>`

これを修正したものを動かすと

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target\debug\gussing_game.exe`
Guess the number!
The secret number is: 79
Please input your guess.
100
You guessed: 100
Too Big!

euledge added a commit that referenced this issue Feb 10, 2019
@euledge
Copy link
Owner Author

euledge commented Feb 10, 2019

ループで複数回の予想を可能にする

複数回実行を行うためにloop ブロックで囲む。
loopから終了するためには数字以外を入れる。

loopブロックで囲むことでインデントのネストを深くする必要がありソースコードが見にくくなってしまう。
rustfmtというコマンドを使用すると推奨されるRustの書式にフォーマットしてくれる。

インストールは以下のコマンドで行う

rustup component add rustfmt

使用方法は、

rustfmt ソースファイル名

@euledge
Copy link
Owner Author

euledge commented Feb 10, 2019

正しい予想をした後に終了する

正しい答えを当てた場合に終了したい。正解を出したら loopをbreakします。
=> の後ろを {} にすることで無名関数として指定できる JavaScriptの書き方と同じ

Ordering::Equal => {
    println!("You Win!");
    break;
}

不正な入力を処理する

parseの処理でpanicとしているものがあるが Result型を返すのでNGの適切な処理を行う。

expectメソッドの呼び出しからmatch式に切り替えることは、 エラーでクラッシュする動作からエラー処理を行う処理に変更する一般的な手段になります。

let guess: i32 = match guess.trim().parse() {
     Ok(num) => num,
     Err(_) => continue
};

このOk値は、最初のアームのパターンにマッチし、このmatch式はparseメソッドが生成し、 Ok値に格納したnumの値を返すだけです。その数値が最終的に、生成している新しいguess変数として欲しい場所に存在します。

parseメソッドは、文字列から数値への変換に失敗したら、エラーに関する情報を多く含むErr値を返します。 このErr値は、最初のmatchアームのOk(num)というパターンにはマッチしないものの、 2番目のアームのErr(_)というパターンにはマッチするわけです。この_は、包括値です; この例では、 保持している情報がどんなものでもいいから全てのErr値にマッチさせたいと宣言しています。

euledge added a commit that referenced this issue Feb 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant