はじめに
Rustのコードを、既存コードの横並びで書くことが多い。きちんとドキュメントを読んだことがないので、The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。
1章:事始め
Hello world
fn main() {
println!("Hello, world!");
}
- Rustのインデントスタイルは4スペース
-
println!
は関数呼び出しではなく、マクロ呼び出し-
let v = vec![1, 2, 3];
は、マクロ無しで書くとv = Vec::new(); v.push(1); v.push(2); ...
と長くなる
-
- RustはAOT (Ahead-Of-Time) コンパイル言語のため、実行可能ファイルを配布可能
- AOTコンパイル:実行前に機械語へ変換。実行時は速いが、ソースコードの状態で配布不可
- JIT (Just in Time) コンパイル:実行時に機械語へ変換。コンパイル時に時間が掛かり最適化もされないが、ソースコードの状態で配布可能
Cargo
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
[dependencies]
- Cargoとは、ビルドシステム&パッケージマネージャ
- 依存ライブラリのダウンロードや、付随するビルドを実施
- 設定ファイルは、TOML (Tom's Obvious Minimal Language) 形式
- 設定ファイルのフォーマットの一種で、名の通りMinimal化されている
-
[package]
:パッケージ情報の定義セクション -
[dependencies]
:依存するパッケージの定義セクション
-
Cargo.lock
はビルド時に生成され、ビルド時の全ライブラリのバージョンが記録 - 主要コマンド
-
cargo new
:プロジェクトを作成 -
cargo build
:プロジェクトをビルド -
cargo run
:プロジェクトのビルドと実行を1ステップで実施 -
cargo check
:バイナリ生成せずプロジェクトをビルド。エラー有無確認などに使う
-
2章:数当てゲームのプログラミング
数を入力するだけの処理
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 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
以下、各行の処理に注目する。
use std::io;
- Rustには多様なライブラリが存在するが、
use
を取り込むことで取り込める - ただし量が多くなることもあるため、Preludeという仕組みも提供している
- Rustのライブラリをリストとして内包し、使いたいトレイトにアクセスしやすくなる
- 例:
use std::io::prelude::*;
let mut guess = String::new();
-
let
:変数定義(変数の束縛) -
mut
:Rustの変数がデフォでimmutable(不変)のため、mutable(可変)として定義 -
::
:関連関数。String
に関連付けられた関数new
を呼び出し、戻り値をguess
に束縛
io::stdin()
.read_line(&mut guess)
-
io::stdin()
:標準入力のためのインスタンスを返却 -
read_line(&mut guess)
:ユーザからの入力を得て、内容をguess
に格納-
&
は引数が参照であることを表し、参照自体が可変。メモリに複数書き込みをせずとも、他のところから同じデータにアクセス可能 - Rustは変数だけではなく、参照もデフォルトで不変
-
-
read_line
:Result型(列挙型)のOk
またはErr
を返す
.expect("Failed to read line");
-
expect
:ReturnされたResult型の結果がOk
の場合に値を返し、Err
の場合はプログラムが終了 -
expect
無しでは、返ってきたResult
が使われていないことが警告される
乱数を生成・比較するロジックを加える
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.");
println!("The secret number is: {}", secret_number);
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
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
以下、各行の処理に注目する。
use rand::Rng;
-
rand
クレートを使うために、tomlファイルにrand = "0.8.3"
を追加-
^0.8.3
の省略記法であり、0.8.3以上0.9.0未満のバージョンを表す
-
- Cargoはセマンティックバージョニングに基づいて取得するパッケージを決定 (X.Y.Z)
- X:後方互換のないメジャーバージョン
- Y:後方互換のあるマイナーバージョン
- Z:後方互換のあるパッチパージョン
- 常に決まったクレートを使いたければ、
Cargo.lock
をGit管理にする - Cargoはリストアップされているクレートをチェックし、未取得対象があればダウンロード。取得済のクレートは、再度ダウンロードすることがない
- 参照先はcretes.io (https://crates.io/)
-
cargo update
でバージョンアップデートし、lockファイルに記録
let secret_number = rand::thread_rng().gen_range(1..101);
-
rand::thread_rng
:乱数生成器の初期化 -
gen_range(low, high)
:lowからhigh-1の範囲内で乱数を生成
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
- 入力した値の
guess
はString型、一方でsecret_number
は数値型であるため型不一致。そこで、String
が用意するparse
で数値型に変換 -
parse
はResult
を返すため、match
のErr
時(文字列の入力)に再入力させている-
_
はすべての値にマッチ
-
-
trim()
はEnter入力時に改行されるため、\n
を消すために使用 -
guess
の再代入部分はシャドーイングと呼び、同じスコープ内の変数が再宣言可能-
let mut
を宣言して値を変えるのとは性質が異なり、使用する型自体を変更可能。よって、値は同じだけれど、異なる方として定義したい場合に使用
-
- どの型としてシャドーイングしたいかを宣言するため、
u32
は明示的に必要-
secret_number
もu32
であるべきと推論できる
-
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
- 入力値
guess
と、乱数で生成したsecret_number
を比較し、Ordering
の列挙子を返却 -
match
は式がパターンと一致するか判定-
パターン => 式
をアームと呼ぶ - 取り得る値は全て網羅する必要があり、必要に応じて
_
を使う
-
-
Ordering
はLess
,Greater
,Equal
を持つ列挙型