Oshiro-lab.

Rust 学習記 #2 - Programming a Guessing Game

2024-04-05

https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html

引き続き同じリポジトリ内で進めていく。

Processing a 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);
}

io ライブラリ

use std::io;

ユーザの入出力を行う io ライブラリが提供されている。

std (標準ライブラリ) に含まれているので use std::io; でスコープに入れる。

fn - 関数宣言

fn main() {}

fn syntax は関数宣言、他の言語のように () で引数を受けることができる。

let - 変数宣言

let hours = 12;  // immutable
let mut age = 24;  // mutable

let statement で変数を宣言する。

基本は immutable で mut を付けると mutable になる。

:: - 関連関数 (associated function)

let mut guess = String::new();

上記コードにおける :: syntax は newString 型の関連関数 (associated function) であることを示す。

String::new() で新しい空の文字列を生成する。

ユーザの入力を受け取る

io::stdin()
	  .read_line(&mut guess)

io モジュールの stdin 関数でユーザ入力を処理できる。

& は引数が参照であることを示す。参照を用いることで同じデータを何度もメモリにコピーせずに済む。

immutable ならば &guess 、mutable ならば &mut guess と書く。

Result 型

    .expect("Failed to read line");

前述の read_line() で入力を受けると同時に値 (io::Result) を返す。

標準ライブラリには、汎用の Resultio::Result などが存在する。

これらの Result 型は enum で、列挙子は OkErr の 2 つ。

例えば read_line メソッドが失敗したとき io::Result インスタンスが Err を返すので、

expect メソッドに渡されている引数のメッセージ (msg) を表示する。

プレースホルダー

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

{} はプレースホルダーで、フォーマット文字列内に複数使うこともできる。

上記コードでは guess の値がプレースホルダーに設定されて表示する。

let i = 1729;
let j = 628;

println!("i = {}, j = {}", i, j);

実行すると i = 1729, j = 628 と出力する。

rand クレートを使用する

Cargo.toml の [dependencies] 内に rand クレートを追加する。

[dependencies]
rand = "0.8.5"

追加後は cargo build コマンドでビルドする。

$ cargo build
   Compiling libc v0.2.153
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.17
   Compiling getrandom v0.2.12
   Compiling rand_core v0.6.4
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling learning_rust v0.1.0 (/home/oshiroman/projects/learning_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 1.07s

rand クレートのように外部依存を持つと、Cargo はそれらの依存関係をたどって、

最新のバージョンをレジストリ ( https://crates.io/ )から取得する。

Cargo.lock

コードから同じ生成物をリビルドするための仕組みを提供している。

Cargo.lock ファイルにはすべての依存関係のバージョンが記載されており、これによって再現性を保つ。

クレートをアップグレードしたい場合は cargo update コマンドを実行する。

ただしマイナーバージョンが変わるリリースは対象外となるので、

マイナーバージョン以上の変更を入れたい場合は Cargo.toml ファイルを編集する必要がある。

乱数生成

use rand::Rng;

let secret_number = rand::thread_rng().gen_range(1..=100);

Rng trait をスコープに入れて、乱数生成を行えるようにする。

今回は 1 から 100 までの整数を生成したいので 1..=100 または 1..101 と指定する。

数字の比較

use std::cmp::Ordering;

match guess.cmp(&secret_number) {
    Ordering::Less => println!("Too small.."),
    Ordering::Greater => println!("Too big..."),
    Ordering::Equal => println!("YOU WIN!!!"),
}

std::cmp::Ordering 型を用いて比較を行う。

OrderingLess / Greater / Equal を列挙子として持つ enum で、2 つの値を比較したときに得られる結果。

cmp メソッドで比較を行い、その結果に応じて match 式の評価を行う。

型変換

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

secret_numberi32 型に対して、入力された値は String 型であるため、

このままでは比較を行うことはできない。

trim メソッドで文字列 guess の空白 (先頭と末尾) を削除し、

parse メソッドで文字列を数値へ変換する。u32 型は符号なし 32 ビット整数を表す。

ループと終了処理

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;
        }
    }
}

loopbreak でループとそれを抜ける処理を書く。

正解したときにループを抜けたいので Ordering::EqualOk のときに break を実行する。

不正な入力を処理

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

expect でクラッシュさせるようにしていた部分を match 式を用いてエラー処理へと書き換える。

数値へ変換できない値を入力した場合は Err が評価されて continue が実行される。

最終的に出来上がったコード

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("guess the number!");
    
    let secret_number = rand::thread_rng().gen_range(1..=100);

    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;
            }
        }
    }
}
(c) 2015 - 2024 Tatsuya Oshiro