百鬼夜行

素性を知られぬままエンジニアになることを目指します

「The Book」で Rust 入門 2 後半

前回のあらすじ

yagyosan.hatenablog.com

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

ユーザが入力した値と生成した乱数を比較する。

extern crate rand;

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

fn main() {
    
    // snip

    println!("your gessed: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
  • std::cmp::Ordering: Ordering型もenumで、列挙子はLess, Greater, Equal
  • match guess.cmp(&secret_number): cmpメソッドは2値を比較し、match式で返ってきたOrderingの列挙子に合わせて次の動作を決定
    • match: 複数のアームを持ち、各アームのパターンを順番に照合していき、合わないものは無視される

だが上記の式のままではエラーが出る。

cargo run
   Compiling guessing_game v0.1.0 (file:///Users/yagyosan/develop/rust_study/guessing_game)
error[E0308]: mismatched types
  --> src/main.rs:23:21
   |
23 |     match guess.cmp(&secret_number) {
   |                     ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integral variable
   |
   = note: expected type `&std::string::String`
              found type `&{integer}`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: Could not compile `guessing_game`.

To learn more, run the command again with --verbose.

型の不一致ということなので、下記の行を追加する。

let guess: u32 = guess.trim().parse()
    .expect("Please type a number!");
  • 型推論にも対応しているが、数値型の場合i32がデフォ
  • 文字列と数値型を比較できなかった
  • 新しい値で変数の値を覆い隠す(Shadow)ことが許されるので、型を変えるときはよく使われる
  • trimメソッドが必要なのは、read_lineの入力値が文字列で5\nで改行を含むため
  • u32型は小さな非負整数にぴったり
  • parseメソッドの呼び出しはResult型を返すのでexpectメソッドで扱う

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

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!("your gessed: {}", guess);

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

これだと無限ループなので変更していく。また文字入力するとクラッシュするが、その内容は下記。

thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', libcore/result.rs:945:5
# mainスレッドでは数値を入力してください
note: Run with `RUST_BACKTRACE=1` for a backtrace.
# 注釈: バックトレースを見るには `RUST_BACKTRACE=1` で走らせて

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

breakを追加して、勝った時に終了するようにする

// --snip--
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
        break;
    }
}

main関数の最後にbreakがあるので、これはプログラムを終了することを意味する。

不正な入力を処理する

非数値を入力した時に、クラッシュさせるのではなく数当てを続けられるようにする。

// --snip--
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);
// --snip--
  • match式に切り替える
  • _は包括値
  • continueloopの次の段階に移り、再度予想入力を求めるようプログラムに指示する
    • 実質的には、parseメソッドが 遭遇しうる全てのエラーを無視する

最後に秘密の数字を表示している行を削除して完了。