何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『The Rust Programming Language 2nd edition』読書記録 その1

定期的に新しいことを初めてすぐに止まることが多いが、めげずに進める。 底本はThe Rust Programming Language 2nd editionである。 なお、自分のための記録であるため、原著の要約を期待して読むとがっかりすると思われる。

はじめに(Introduction)

  • Rustは安全性、速度、並列性に焦点を当てている。
  • Rustは低レベル言語のような性能と制御、高レベル言語のような高度な抽象性をプログラムがもつようにデザインされている。
  • Cのような言語の経験者やより安全なものを探している人、Pythonのような言語の経験者で表現性を犠牲にせずにより良い性能を出したい人に向いている。
  • 安全性のチェックやメモリ管理はコンパイル時に行うので実行時の性能に影響を与えない。他の言語ではうまくいかない多くのユースケース(計算時間とメモリ使用量が予測可能なプログラム、他の言語への埋め込み、デバイスドライバオペレーティングシステムなどの低レベルコードの作成)役に立つ。Webアプリケーションにも向いている。
  • 『The Rust Programming Language』は少なくとも1つの言語でプログラミングをする方法を知っている読者を対象にしている。

よくある読者のやる気を高める宣伝文。一応、今はPythonのプログラムを書くことで生活しているので「少なくとも1つの言語でプログラミングをする方法を知っている読者」とみなしてよいだろう。

インストール(Installation)

Windowsにおけるインストール(Installing on Windows)

Windowsの場合はインストーラーを叩くだけの簡単なお仕事...と思っていたら「Visual C++ 2015 Build Tools」を要求された。 インストーラの指示に従ってインストールする。 デフォルトだとログインしているユーザーのホームディレクトリにインストールされるようである。

$ rustc --version
rustc 1.24.0 (4d90ac38c 2018-02-12)

Rustプログラムの記述と実行(Writing and Running a Rust Program)

  • ファイル名はmain.rs。Rustファイルの拡張子は常に.rs。ファイル名はアンダースコアで区切る。

エディタはひとまずVisual Studio Codeを利用する。IDEがない場合は大抵VS Codeを選択することが多い。

fn main() {
    println!("Hello, world!");
}

rustcコンパイルする。

$ rustc main.rs
$ main.exe
Hello, world!

VC++ランタイムが必要であったとはいえ、同じソースコードでOSに関係なくビルドできるのは便利である。 公式ドキュメント曰く、これで公式のRustプログラムを書いたことになり、Rustプログラマになったのである。 私はRustプログラマである。

Rsutプログラムの解剖学(Anatomy of a Rust Program)

fn main() {

}
  • main函数は特別で実行されるすべてのRustプログラムで最初に実行される。
  • 引数がある場合は()内に書く。
  • 函数の本文は{}内に書く。
    println!("Hello, world!");
  • インデントは4スペースであり、タブではない。
  • println! はRustのマクロと呼ばれるもの。Rustでメタプログラミングを実現している。詳細はAppendix Dで扱い、ひとまず!がマクロであることを押さえておく。
  • 行末は;で終える。

なお、println! とせずprintln とするとコンパイルエラーとなる。

error[E0423]: expected function, found macro `println`
 --> src\main.rs:2:5
  |
2 |     println("Hello, world!");
  |     ^^^^^^^ did you mean `println!(...)`?

error: aborting due to previous error

error: Could not compile `hello_cargo`.

printlnがありません、ではなくprintln!じゃないですか、とエラーメッセージを送ってくれるのはちょっとうれしい。 Hello worldの伝統から最初にI/Oをやるのだが、プログラムにおいてI/Oって結構特殊な部類なのでは、と思ったりもする。

コンパイルと実行は別々のステップである(Compiling and Running Are Separate Steps)

  • Rustは事前コンパイル言語(実行時コンパイラとの対比)
  • 単純なプログラムならrustcで事足りるが、プロジェクトの規模が大きくなったらCargoを使う。

Hello, Cargo!

  • CargoはRustのビルドツール、パッケージマネージャー。コードのビルド、依存ライブラリのダウンロードとライブラリのビルドを行う。
$ cargo --version
cargo 0.25.0 (8c93e0895 2018-02-01)

Cargoで新規にプロジェクトを作成するには cargo new hello_cargo --binと打つ。 --binオプションは実行バイナリを作るためのものである。デフォルトはライブラリとなる。

$ cargo new -h
Create a new cargo package at <path>

Usage:
    cargo new [options] <path>
    cargo new -h | --help

Options:
    -h, --help          Print this message
    --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.
    --bin               Use a binary (application) template
    --lib               Use a library template [default]
    --name NAME         Set the resulting package name, defaults to the value of <path>
    -v, --verbose ...   Use verbose output (-vv very verbose/build.rs output)
    -q, --quiet         No output printed to stdout
    --color WHEN        Coloring: auto, always, never
    --frozen            Require Cargo.lock and cache are up to date
    --locked            Require Cargo.lock is up to date
    -Z FLAG ...         Unstable (nightly-only) flags to Cargo

Cargoは設定ファイルCargo.tomlソースコードディレクトsrcmain.rs、そして.gitignoreを生成する。git以外のバージョン管理ツールを使う場合は--vcsで指定する。

構造としては以下の通りとなる。

│  .gitignore
│  Cargo.toml
│
└─src
        main.rs

ビルドはcargo build、実行するにはcargo run、リリース用のビルドはcargo build --releaseとする。

実際に実行すると大量のファイルが生成される。

  .gitignore
│  Cargo.lock
│  Cargo.toml
│
├─src
│      main.rs
│
└─target
    ├─debug
    │  │  .cargo-lock
    │  │  hello_cargo.d
    │  │  hello_cargo.exe
    │  │
    │  ├─.fingerprint
    │  │  └─hello_cargo-c909bb4d8770b1a8
    │  │          bin-hello_cargo-c909bb4d8770b1a8
    │  │          bin-hello_cargo-c909bb4d8770b1a8.json
    │  │          dep-bin-hello_cargo-c909bb4d8770b1a8
    │  │
    │  ├─build
    │  ├─deps
    │  │      hello_cargo-c909bb4d8770b1a8.d
    │  │      hello_cargo-c909bb4d8770b1a8.exe
    │  │      hello_cargo-c909bb4d8770b1a8.pdb
    │  │
    │  ├─examples
    │  ├─incremental
    │  │  └─hello_cargo-3lmpxjy8e6kta
    │  │      │  s-eyo5s02mrc-1wwhhsl.lock
    │  │      │
    │  │      └─s-eyo5s02mrc-1wwhhsl-3ewijj0am31b3
    │  │              1y16o1qfye96o7m0.o
    │  │              3rngp6bm2u2q5z0y.o
    │  │              4xq48u46a1pwiqn7.o
    │  │              dep-graph.bin
    │  │              f80e96b5kob4hc2.o
    │  │              query-cache.bin
    │  │              work-products.bin
    │  │
    │  └─native
    └─release
        │  .cargo-lock
        │  hello_cargo.d
        │  hello_cargo.exe
        │
        ├─.fingerprint
        │  └─hello_cargo-c9cb027cd6477672
        │          bin-hello_cargo-c9cb027cd6477672
        │          bin-hello_cargo-c9cb027cd6477672.json
        │          dep-bin-hello_cargo-c9cb027cd6477672
        │
        ├─build
        ├─deps
        │      hello_cargo-c9cb027cd6477672.d
        │      hello_cargo-c9cb027cd6477672.exe
        │      hello_cargo-c9cb027cd6477672.pdb
        │
        ├─examples
        ├─incremental
        └─native

Cargoによる実行も--releaseフラグで開発バイナリと本番バイナリを選択することができる。

$ cargo run --release
    Finished release [optimized] target(s) in 0.0 secs
     Running `target\release\hello_cargo.exe`
Hello, world!
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target\debug\hello_cargo.exe`
Hello, world!

rustcコンパイルした時とは出力結果がだいぶ異なるが、その秘密は--verboseフラグを付けるとわかる。

$ cargo build --verbose
   Compiling hello_cargo v0.1.0 (file:///C:/Users/hayao/Documents/rust_projects/hello_cargo)
     Running `rustc --crate-name hello_cargo src\main.rs --crate-type bin --emit=dep-info,link -C debuginfo=2 -C metadata=c909bb4d8770b1a8 -C extra-filename=-c909bb4d8770b1a8 --out-dir C:\Users\hayao\Documents\rust_projects\hello_cargo\target\debug\deps -C incremental=target\debug\incremental -L dependency=target\debug\deps`
    Finished dev [unoptimized + debuginfo] target(s) in 0.98 secs
$ cargo build --release --verbose
   Compiling hello_cargo v0.1.0 (file:///C:/Users/hayao/Documents/rust_projects/hello_cargo)
     Running `rustc --crate-name hello_cargo src\main.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C metadata=c9cb027cd6477672 -C extra-filename=-c9cb027cd6477672 --out-dir target\release\deps -L dependency=target\release\deps`
    Finished release [optimized] target(s) in 0.69 secs

デバッグビルドではデバッグフラグ、本番ビルドでは最適化フラグが立っていることがわかる。

公式でビルドツールが用意されていると安心感がある。 昔、Javaを触っていた時はMavenであった。 Pythonpipが後から公式ツールとなった。

1章はRustの宣伝とコンパイラとビルドツールの基本的な使い方の説明で特に困ることはない。 2章ではHello worldよりもずっと複雑なプログラムを行っていく。