Rustの勉強 2日目
Rustlingsの環境設定をする
公式サイト に教材としてRustlingsへのリンクがあったのでとりあえずそれをやることにした。
$ curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash -s rustlings
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4591 100 4591 0 0 17429 0 --:--:-- --:--:-- --:--:-- 18146
Let's get you set up with Rustlings!
Checking requirements...
SUCCESS: Git is installed
SUCCESS: cc is installed
SUCCESS: rustup is installed
SUCCESS: Rust is installed
SUCCESS: Cargo is installed
SUCCESS: Rust is up to date
Cloning Rustlings at rustlings...
Checking out version tags/5.2.1...
Installing the 'rustlings' executable...
Installing rustlings v5.2.1 (/Users/yoshifumi/personal/rustlings)
Updating crates.io index
Downloaded argh v0.1.9
Downloaded fsevent v0.4.0
Downloaded notify v4.0.17
Downloaded indicatif v0.16.2
Downloaded toml v0.5.9
Downloaded number_prefix v0.4.0
Downloaded argh_derive v0.1.9
Downloaded console v0.15.1
Downloaded argh_shared v0.1.9
Downloaded glob v0.3.0
Downloaded filetime v0.2.17
Downloaded walkdir v2.3.2
Downloaded terminal_size v0.1.17
Downloaded same-file v1.0.6
Downloaded fsevent-sys v2.0.1
Downloaded 15 crates (315.8 KB) in 0.94s
Compiling proc-macro2 v1.0.44
Compiling unicode-ident v1.0.4
Compiling libc v0.2.133
Compiling quote v1.0.21
Compiling syn v1.0.101
Compiling serde_derive v1.0.145
Compiling memchr v2.5.0
Compiling serde v1.0.145
Compiling unicode-width v0.1.10
Compiling regex-syntax v0.6.27
Compiling argh_shared v0.1.9
Compiling heck v0.4.0
Compiling serde_json v1.0.85
Compiling cfg-if v1.0.0
Compiling once_cell v1.15.0
Compiling bitflags v1.3.2
Compiling same-file v1.0.6
Compiling ryu v1.0.11
Compiling number_prefix v0.4.0
Compiling itoa v1.0.3
Compiling lazy_static v1.4.0
Compiling home v0.5.3
Compiling glob v0.3.0
Compiling walkdir v2.3.2
Compiling aho-corasick v0.7.19
Compiling regex v1.6.0
Compiling terminal_size v0.1.17
Compiling fsevent-sys v2.0.1
Compiling filetime v0.2.17
Compiling console v0.15.1
Compiling fsevent v0.4.0
Compiling notify v4.0.17
Compiling indicatif v0.16.2
Compiling argh_derive v0.1.9
Compiling argh v0.1.9
Compiling toml v0.5.9
Compiling rustlings v5.2.1 (/Users/yoshifumi/personal/rustlings)
Finished release [optimized] target(s) in 15.28s
Installing /Users/yoshifumi/.cargo/bin/rustlings
Installed package `rustlings v5.2.1 (/Users/yoshifumi/personal/rustlings)` (executable `rustlings`)
All done! Run 'rustlings' to get started.
さて始めるかと思ってプロジェクトルートのREADMEを見てみたら、とりあえず exercise
ディレクトリにREADMEがあるからそれを読め、と書いてある。で exercise/README
を見たらぶっきらぼうに次の表が置いてあった。
# Exercise to Book Chapter mapping
| Exercise | Book Chapter |
| ---------------------- | ------------------- |
| variables | §3.1 |
| functions | §3.3 |
| if | §3.5 |
| primitive_types | §3.2, §4.3 |
| vecs | §8.1 |
| move_semantics | §4.1, §4.2 |
| structs | §5.1, §5.3 |
| enums | §6, §18.3 |
| strings | §8.2 |
| modules | §7 |
| hashmaps | §8.3 |
| options | §10.1 |
| error_handling | §9 |
| generics | §10 |
| traits | §10.2 |
| tests | §11.1 |
| lifetimes | §10.3 |
| standard_library_types | §13.2, §15.1, §16.3 |
| threads | §16.1, §16.2, §16.3 |
| macros | §19.6 |
| clippy | n/a |
| conversions | n/a |
Book Chapter
と書いてあるのはおそらくプロジェクトルートのREADMEに書いてあった「初心者ならこれを読め」という公式の本だと思うので、仕方ないからそれを読み進めていくことにする。
少し版が古いけど日本語版もあった。
The Rust Programming Language 日本語版を読んでいく
まえがき
ざっと眺めておしまい
はじめに
ざっと眺めて、頭から読んでけばいいことがわかったのでそのとおりにする。
事始め
「1.1 インストール」の節はもうインストールは終わってるので飛ばした。
「1.2 Hello, World!」も公式サイトのGetting Startedでやった内容が前半なので飛ばす。後半でいくつか確認事項を理解。
-
main
関数がエントリーポイント - 関数は必ず波括弧でブロックを書く必要がある
- インデントは4スペース
-
!
はマクロの呼び出し
ナイーブに rustc
でコンパイルする方法をここで紹介してるけど、依存解決とかが面倒になるからあとでCargoを使ってビルドする方法を紹介すると言って終わり。
「1.3 Hello, Cargo!」で、直前の節の話題を回収する。
まず、前提として「CargoはRustにおけるビルドシステム兼パッケージマネージャ」であるという理解ができた。
cargo new <project name>
で新しいディレクトリを作成して、必要なファイルのスケルトンを作ってくれる(Cargo.toml
と src
サブディレクトリ)。
cargo build
で、依存関係も含めてビルドしてくれる。このとき、細々とした依存関係のバージョンを Cargo.lock
というファイルを作って記録してくれる。
cargo run
を行うと、cargo build
したあとにプログラム自体の実行まで行ってくれる。ここまでの流れはGoでいうところの go build
と go run
に似ている。
数当てゲームのプログラミング
いよいよハンズオン形式のプロジェクトに入ったので進めていく。
cargo new gussing_game
cd guessing_game
で、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);
}
実行してみる
$ cargo run
Compiling guessing_game v0.1.0 (/Users/yoshifumi/personal/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
10
You guessed: 10
その後、解説が書いてある
- 標準ライブラリのうち、自動で取り込まれるものが prelude と呼ばれる
- それ以外の場合は
use
文で使用を宣言する -
let
キーワードで変数を宣言し、初期値を束縛する。変数はデフォルトではイミュータブル。変数をミュータブルで宣言したかったらmut
キーワードも付ける必要がある。 -
::
は型の関連関数(associated function)の呼び出しに使う。 -
&
は参照。read_line
は&mut String
を受け取るので、&mut guess
を渡す。ここで注意すべきは参照もデフォルトはイミュータブルなので、参照をミュータブルにしたかったら&mut
をつける必要がある。(ここでは&guess
はダメ) -
read_line
はio::Result
という結果型を返し、結果型はOk
かErr
となるEnum型である。io::Result
型にはexpect
というメソッドがあるので、これを呼び出してエラー処理をする。-
Ok
だった場合にはexpect
はただ読み込んだバイト数を返す。
-
-
{}
はprintln!
マクロのプレースホルダー。
「秘密の数字を生成する」の節に来た。手で Cargo.toml
を修正して rand
クレートを入れる説明をしているが、昨日の設定で cargo-edit
をインストールしたので次のコマンドで入れてしまおうと思う。そしてそのまま本文と同様に cargo build
まで行う。
$ cargo add rand
Updating crates.io index
Adding rand v0.8.5 to dependencies.
Features:
+ alloc
+ getrandom
+ libc
+ rand_chacha
+ std
+ std_rng
- log
- min_const_gen
- nightly
- packed_simd
- serde
- serde1
- simd_support
- small_rng
$ cat Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
$ cargo build
Downloaded ppv-lite86 v0.2.16
Downloaded rand_chacha v0.3.1
Downloaded rand_core v0.6.4
Downloaded getrandom v0.2.7
Downloaded rand v0.8.5
Downloaded 5 crates (176.1 KB) in 0.73s
Compiling libc v0.2.133
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.16
Compiling getrandom v0.2.7
Compiling rand_core v0.6.4
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
Compiling guessing_game v0.1.0 (/Users/yoshifumi/personal/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 3.27s
main.rs
のコードは変更していないけれども、Cargo.toml
の [dependencies]
に rand
が入ったので、 cargo build
でレジストリから rand
とその依存クレートを持ってきているのが分かる。
あとの説明で Cargo.lock
はGoで言うところの go.sum
みたいなものだとわかった。依存しているクレートを更新したい場合には cargo update
で更新可能。
ここまでわかったところでいよいよ main.rs
を変更する。
use rand::Rng;
use std::io;
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);
}
このコードについての解説で知ったことは
-
rand::Rng
というの型ではなくトレイト(あとで解説されるとのこと。どうも特定のメソッド群をまとめたものっぽい?) - 範囲式という
開始..終了
の形式の書き方で数値の範囲を指定
これを実行して、数字が実行ごとにランダムに変わっていることがわかった。
$ cargo run
Compiling guessing_game v0.1.0 (/Users/yoshifumi/personal/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/guessing_game`
Guess the number!
The secret number is 83
Please input your guess.
8
You guessed: 8
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/guessing_game`
Guess the number!
The secret number is 14
Please input your guess.
3
You guessed: 3
いよいよこれで乱数と入力値を比較してゲームにする部分を実装する。コードを解説読みながら実装した。
- Rustは強い型付けなので型が合わないとコンパイルできない。
- 変数を型推論でなく型付きで宣言するときは
let 変数名: 型名
で宣言。 - Rustでは変数のシャドーイングが可能。違う型でも問題ない。
-
match
キーワードでパターンマッチが可能。各アーム(条件節)はカンマ区切りでつなぐ。
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..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!");
break
},
}
}
これを実行して、ちゃんと動作することを確認。
cargo run
Compiling guessing_game v0.1.0 (/Users/yoshifumi/personal/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
Running `target/debug/guessing_game`
Guess the number!
The secret number is 92
Please input your guess.
92
You guessed: 92
You win!
次は繰り返しの実装。ただ繰り返しを実行するだけだと終了できなくなってしまったり、不正入力があると意図せずプログラムが終了してしまうので、以下のように修正する。
-
guess.trim().parse()
の返り値でmatch
して、Result型でパターンマッチ。-
Err
型の値に興味がない場合はアンダースコアでスルーできる。
-
-
guess.cmp()
の返り値がOrdering::Equal
だった場合にbreak
する- パターンマッチで複数の処理をさせたい場合にはブロックを定義すれば良い。
すると次のようになる。
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..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;
}
};
}
}
これを実行すると
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/guessing_game`
Guess the number!
The secret number is 91
Please input your guess.
11
You guessed: 11
Too small!
Please input your guess.
100
You guessed: 100
Too big!
Please input your guess.
91
You guessed: 91
You win!
ちゃんと動いた。最後に、最初の方で秘密の数字を表示している行を消して完成。
cargo run
Compiling guessing_game v0.1.0 (/Users/yoshifumi/personal/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.21s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
1
You guessed: 1
Too small!
Please input your guess.
100
You guessed: 100
Too big!
Please input your guess.
50
You guessed: 50
Too big!
Please input your guess.
25
You guessed: 25
Too big!
Please input your guess.
12
You guessed: 12
Too small!
Please input your guess.
19
You guessed: 19
Too big!
Please input your guess.
16
You guessed: 16
Too big!
Please input your guess.
14
You guessed: 14
Too big!
Please input your guess.
13
You guessed: 13
You win!
無事に2章終了。わかりやすかった。
Discussion