自己紹介
出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。
Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。
環境
新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell
前回
前回はRustの紹介と、環境構築まで行いました。
Rust勉強中 - その1
ハロワ
環境構築が終わったので、「Hello, world!」を表示してみます。
まず、プロジェクトの作成をcargo new
コマンドで行うみたいです。
$ cargo new --help
Create a new cargo package at <path>
USAGE:
cargo.exe new [OPTIONS] <path>
OPTIONS:
-q, --quiet No output printed to stdout
--registry <REGISTRY> Registry to use
--vcs <VCS> Initialize a new repository for the given version control system (git, hg, pijul, or
fossil) or do not initialize any version control at all (none), overriding a global
configuration. [possible values: git, hg, pijul, fossil, none]
--bin Use a binary (application) template [default]
--lib Use a library template
--edition <YEAR> Edition to set for the crate generated [possible values: 2015, 2018]
--name <NAME> Set the resulting package name, defaults to the directory name
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
ARGS:
<path>
$ cargo new --bin hello # --binはデフォルトなので不要
$ Get-ChildItem -Force hello
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--h-- 2019/09/17 15:18 .git
d----- 2019/09/17 15:18 src
-a---- 2019/09/17 15:18 19 .gitignore
-a---- 2019/09/17 15:18 221 Cargo.toml
$ Get-ChildItem -Force .\src\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2019/09/17 15:18 45 main.rs
--binは実行バイナリとして作成するという意味です。他にもライブラリとして作成する--libオプションもあるようです。
コメントにも書きましたが、--binはデフォルトなので付けても付けなくても良いです。
cargoはバージョン管理システムのgitを用います。なので、.gitや.gitignoreが自動で作成されました。自動で作成しないでほしいときは、--vcs none
と指定します。
実際のRustソースコードはsrcに書きます。すでにmain.rsというファイルが作成されています。
※ windowsなのでlsではなくdirやGet-ChildItemです。(まだ慣れへん)
では、main.rsの中身を見てみます。
fn main() {
println!("Hello, world!");
}
すでにHello, world!を表示するソースコードが出来ています。
実行するにはcargo run
を使用します。
$ cargo run --help
Run a binary or example of the local package
USAGE:
cargo.exe run [OPTIONS] [--] [args]...
OPTIONS:
-q, --quiet No output printed to stdout
--bin <NAME>... Name of the bin target to run
--example <NAME>... Name of the example target to run
-p, --package <SPEC> Package with the target to run
-j, --jobs <N> Number of parallel jobs, defaults to # of CPUs
--release Build artifacts in release mode, with optimizations
--features <FEATURES> Space-separated list of features to activate
--all-features Activate all available features
--no-default-features Do not activate the `default` feature
--target <TRIPLE> Build for the target triple
--target-dir <DIRECTORY> Directory for all generated artifacts
--manifest-path <PATH> Path to Cargo.toml
--message-format <FMT> Error format [default: human] [possible values: human, json, short]
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
ARGS:
<args>...
If neither `--bin` nor `--example` are given, then if the package only has one
bin target it will be run. Otherwise `--bin` specifies the bin target to run,
and `--example` specifies the example target to run. At most one of `--bin` or
`--example` can be provided.
All the arguments following the two dashes (`--`) are passed to the binary to
run. If you're passing arguments to both Cargo and the binary, the ones after
`--` go to the binary, the ones before go to Cargo.
$ cargo run
cargo run
Compiling hello v0.1.0 (C:\Users\deta\hack\rust\hello)
Finished dev [unoptimized + debuginfo] target(s) in 1.20s
Running `target\debug\hello.exe`
Hello, world!
$ ls .\target\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2019/09/17 16:06 debug
-a---- 2019/09/17 16:06 1181 .rustc_info.json
$ ls .\target\debug\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2019/09/17 16:06 .fingerprint
d----- 2019/09/17 16:06 build
d----- 2019/09/17 16:06 deps
d----- 2019/09/17 16:06 examples
d----- 2019/09/17 16:06 incremental
d----- 2019/09/17 16:06 native
-a---- 2019/09/17 16:06 0 .cargo-lock
-a---- 2019/09/17 16:06 96 hello.d
-a---- 2019/09/17 16:06 153600 hello.exe
-a---- 2019/09/17 16:06 1388544 hello.pdb
実行すればcargoがコンパイル&実行まで行ってくれます。また、target/debugには実行ファイル(hello.exe)やデバッグ用のファイル(hello.pdb)などが作成されていました。これらコンパイルによって出来たフォルダやファイルはcargo clean
で削除できます。
$ cargo clean --help
Remove artifacts that cargo has generated in the past
USAGE:
cargo.exe clean [OPTIONS]
OPTIONS:
-q, --quiet No output printed to stdout
-p, --package <SPEC>... Package to clean artifacts for
--manifest-path <PATH> Path to Cargo.toml
--target <TRIPLE> Target triple to clean output for
--target-dir <DIRECTORY> Directory for all generated artifacts
--release Whether or not to clean release artifacts
--doc Whether or not to clean just the documentation directory
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
If the `--package` argument is given, then SPEC is a package ID specification
which indicates which package's artifacts should be cleaned out. If it is not
given, then all packages' artifacts are removed. For more information on SPEC
and its format, see the `cargo help pkgid` command.
$ cargo clean
$ ls
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2019/09/17 16:03 src
-a---- 2019/09/17 15:18 19 .gitignore
-a---- 2019/09/17 16:06 137 Cargo.lock
-a---- 2019/09/17 15:18 221 Cargo.toml
cargo clean
の実行でtargetフォルダが削除されていることがわかります。
関数
ここまででプロジェクト作成から実行までの基本的な流れは理解できました。
今度は簡単な関数の定義をし、その中身を見ていきます。
fn sum(mut a: i32, b: i32) -> i32 {
assert!(a != 0 && b != 0);
a = a + b;
a
}
fn main() {
let a = 1;
let b = 1;
let result = sum(a, b);
println!("sum(a, b) = {}", result);
}
$ cargo run
Compiling sum v0.1.0 (C:\Users\deta\hack\rust\sum)
Finished dev [unoptimized + debuginfo] target(s) in 0.83s
Running `target\debug\sum.exe`
sum(a, b) = 2
関数の定義は「fn」を使います。「ファン」と呼ぶようです。ここではmain関数とsum関数を定義しました。main関数は実行時に最初に呼ばれる関数で、sum関数はmain関数内で呼ばれます。
main関数について
...
fn main() {
let a = 1;
let b = 1;
let result = sum(a, b);
println!("sum(a, b) = {}", result);
}
main関数の流れは以下です。
- 変数aを1で初期化
- 変数bを1で初期化
- sum関数の引数にaとbを設定して呼び、結果を変数resultに格納
- 変数resultをprintln!マクロで出力
letでローカル変数を宣言します。Javascriptでもletを使いますよね。このとき、変数の使われ方から型推論が行われるため(マジか!)、ここでは型を書く必要がありませんでした。もし、型を書く場合はlet a: i32 = 1;
と書きます。ただし、型推論は関数内でしか行わないため、関数の仮引数や戻り値には型を書く必要があるようです。
先のハロワの例でも登場しました「println!」ですが、末尾に!がついているとマクロの呼び出しという意味になるそうです。なので次に説明するsum関数内のassert文にも!が付いています。また、println!はPythonのformat関数のようなフォーマットを指定をしていますね。「{}」にresultが挿入される形で出力されています。そのほかにも0埋めや16進数で出力したりなど細かい指定ができるようです。ここで私はとても親しみを感じました。
補足:
そもそもマクロって何やねんと思ったので調べました。マクロとは、コンパイル前に行われる処理(プリプロセス)で、あらかじめ決められた文を命令に変換することだそうです。
sum関数について
fn sum(mut a: i32, b: i32) -> i32 {
assert!(a != 0 && b != 0);
a = a + b;
a
}
...
sum関数の流れは以下です。
- assert!マクロで引数チェック
- 変数aと変数bを足した値を変数aに代入
- 変数aを返す
まず、仮引数aの宣言で「mut」が付いています。mutable(可変)の略で「ミュート」と呼ぶそうです。Rustでは一度初期化した変数は値を変更することはできないそうです。ただし、mutを付けることでその関数内で変数の変更が可能になるということです。今回のsum関数の場合、変数aのみが変更されるのでmutを付けました。仮引数名とコロン(:)の後にi32という型が書いてあります。これは符号あり32ビット整数という意味です。他にもi64(符号あり64ビット整数)やu32(符号なし32ビット整数)、u64(符号なし32ビット整数)、f32(単精度浮動小数)、f64(倍精度浮動小数)などがあります。矢印(->)の後にも型が書かれています。これは、戻り値の型です。
次に何となくassert!マクロを入れました。assert!は引数が真かどうかチェックし、偽であればメッセージを出力します。試しに、変数aに0を設定して、sum関数を呼び出してみました。
fn sum(mut a: i32, b: i32) -> i32 {
assert!(a != 0 && b != 0);
a = a + b;
a
}
fn main() {
let a: i32 = 0;
let b: i32 = 1;
let result = sum(a, b);
println!("sum(a, b) = {}", result);
}
$ cargo run
Compiling sum v0.1.0 (C:\Users\deta\hack\rust\sum)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target\debug\sum.exe`
thread 'main' panicked at 'assertion failed: a != 0 && b != 0', src\main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: process didn't exit successfully: `target\debug\sum.exe` (exit code: 101)
非常にわかりやすいメッセージが出力されています。「src\main.rsの2行目でスレッドmainがパニクった」そうです。このように予期せぬ終了をpanic(パニック)というそうです。assert!はスキップできませんが、debug_assert!は実行速度重視でコンパイルされたとき(リリースビルド)はスキップされるようです。
そして、変数aと変数bを足し合わせた結果を変数aに代入します。
最後に、変数aを出力します。ここで、よく見ていただきたいのが、ほとんどの文にはセミコロン(;)が付きますが、関数内の式でセミコロンが付かなかった場合は、それが戻り値として扱われます。Rustにもreturn文はあり、使いどころとしては、関数の途中で明示的にリターンしたいときにしか使用しないそうです。
ブロック式というものもあります。中括弧({})で囲まれた式は一つのブロックとして実行されます。先ほどのsum.rsでブロック式を使った例を追加してみました。
fn sum(mut a: i32, b: i32) -> i32 {
assert!(a != 0 && b != 0);
a = a + b;
a
}
fn main() {
let a: i32 = 1;
let b: i32 = 1;
let result = sum(a, b);
println!("sum({}, {}) = {}", a, b, result);
let result2 = {
println!("Executing in block.");
sum(result, b)
};
println!("sum({}, {}) = {}", result, b, result2);
}
$ cargo run
Compiling sum v0.1.0 (C:\Users\deta\hack\rust\sum)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target\debug\sum.exe`
sum(1, 1) = 2
Executing in block.
sum(2, 1) = 3
ブロック内の式の実行結果を変数result2に格納します。ブロック内のsum関数にはセミコロンがないので、そのブロック内での戻り値として返します。
今回はここまで。
今回印象的やったのはmutです。mutによって変数の予期しない変更を防いでいるのかもしれません。