cargoのソースを読んでみよう - 第1回 : main関数はどこだ

# 三行で頼む

- `main`関数の「正しい」見つけ方とは
- cargoのターゲットまわりは結構複雑。結論だけ知りたいなら
 [binターゲットについてのまとめ](#binターゲットについてのまとめ)を参照。
- `main`関数のソースの位置を知りたいだけなら`cargo metadata --no-deps | jq '.packages[].targets[] | select(.kind==["bin"])'`が楽よ

# 前提条件

[前回](http://qiita.com/AoiMoe/items/a532bb0e554c8060103c)に引き続き、次の環境を想定しています:

- 使用コンパイラ: rustc 1.15.1
- 読むソース: cargo 0.16.0

また、[例の本](https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/README.html)は一読していることも想定しています。


# 前回の補足

## 補足1: 使用コンパイラについて

前回からだいぶ時間が経って、現行のstableリリースは1.17.0となっているわけですが、ここで使用しているのは1.15.1ですので、次のようにして1.15.1のバイナリとソースを明示的にインストールしておくことをお勧めします:

```shell-session
% rustup toolchain install 1.15.1
% rustup component add --toolchain 1.15.1 rust-src
```

なお、racer-emacsが参照するソースをこのバージョンに設定するためには、emacsを起動するときにRUST_SRC_PATHを設定するか、racer-rust-src-pathをcustomize-set-variableすると可能なようですが、正直面倒くさいので、

```shell-session
% rustup default 1.15.1
```

としてしまっています。cargoプロジェクトごとに使うコンパイラを切り替えられて、それが自動的にracer-emacsにも反映されると楽なんですけどねえ。

なお、`rustup toolchain`であらかじめインストールされていないバージョンのtoolchainを`rustup default`でデフォルトに設定すると、次に別のバージョンをデフォルトにした時に破棄されてしまい、毎回ダウンロードすることになるようなので、`rustup default`する前に明示的にバージョンを指定して`rustup toolchain install`しておいた方がいいようです。

### 追記(2017-07-03)

何気なくrustupコマンドのusageを見ていたら、`rustup override`という機能があることに気づきました。

```shell-session:
% cd cargo-0.16.0
% rustup override set 1.15.1
% cd ..
% cd cargo-0.17.0
% rustup override set 1.16.0
...
```

とかやっておくと、それぞれのソースに対して使用するtoolchainのバージョンが設定できます。また、racer-emacsもこの設定を見てくれるようです。ただし、一つのEmacsプロセスの中では異なるバージョンを混在させることはできないようですが。


## 補足2: ビルドについて

前回、「あらかじめ`cargo build`しておくと良い」と書きましたが、オプションなしで`cargo build`を実行すると、各crateについて、`Cargo.toml`に記述されているバージョンに対して互換性のある中で最新のものを取得してくるため、コードを読むという観点では参照先が安定しないことになります。cargo 0.16.0リリース時点で使用が想定されていたcrateのバージョンは`Cargo.lock`に保存されており、次のように`--locked`オプションを付けてビルドすると、このバージョンがダウンロードされます:

```shell-session
% cargo build --locked
```

なお、以前に`--locked`を付けずに`cargo build`してしまった場合には、`Cargo.lock`が更新されてしまっている場合があるため、一度ワーキングディレクトリをrevertしてやり直すとよいでしょう:

```shell-session
% git reset --hard
% cargo build --locked
```


# はじめに

ソースを読む場合、その目的によって、どこから読み始めるのかが変わってくるのですが、大きく分けると正面から攻める方法と裏口から攻める方法があります。何か特定の調べごとがある場合は裏口からショートカットするのですが、今回は**実用的なアプリケーションの標準的な書き方の作法を知る**のが目的なので、正面から攻めることにします。つまり、しばらくプログラムのエントリポイントから順番に読んで行こうと思っているところです。

さて、[例の本](https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/README.html)を読むと、Rustのプログラムの[エントリポイントは`main`関数](https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/getting-started.html#rustプログラムの解剖)だということが分かります。そこで、まずは`main`関数がどのファイルに書かれているかを探すことになります。まあ、名前が分かってるのだからgrepで候補を絞り込んで目視で探せば一発で見つかるわけですが:

```shell-session
% grep -r "fn main(" .
...大量の偽マッチ...
./src/bin/cargo.rs:fn main() {
./src/cargo/util/toml.rs:    fn main(&self) -> Option<&PathBuf> {
./src/cargo/ops/cargo_new.rs:fn main() {
./src/doc/build-script.md:fn main() {
./src/doc/build-script.md:fn main() {
./src/doc/build-script.md:fn main() {
./src/doc/build-script.md:fn main() {
./src/doc/build-script.md:fn main() {
./src/doc/index.md:fn main() {
./src/doc/guide.md:fn main() {
./src/doc/guide.md:fn main() {
```

というわけで、`src/bin/cargo.rs`が目的のファイルだろうと想像できますね。おわり。

……我々プログラマが目の前の仕事を片付ける場合には、こういうアドホックな方法で十分なのですが、今回の目的は、**標準的な書き方の作法を知ること**なので、「プロジェクトディレクトリに大量に存在する`*.rs`ファイルのうち、cargoはどうやってこの`src/bin/cargo.rs`にたどり着き、この`main`関数をエントリポイントとして扱うのか」というところまで掘り下げてみたほうがいいんじゃないかなあと思います。


# ビルドシステムとしてのcargoの振る舞い

## ドキュメントを見てみる

cargoは他の多くのRustアプリケーションと同様に、ビルドシステムとしてcargoそれ自身(もっといえば`cargo build`サブコマンド)を採用しています。cargoの基本的な使い方は、例の本の初っ端、[Hello, Cargo!](https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/getting-started.html#hello-cargo)に書いてあるので読んでると思います。しかし、例の本の中には、cargoプロジェクトのファイル構成についてあまり詳しくは述べられておらず、

> Cargoはソースファイルが src ディレクトリにあるものとして動く

とか

> 今回の例では実行可能ファイルを作るので main.rs の名前を引き続き使います。 もしライブラリを作りたいなら lib.rs という名前にすることになります。 この規約はCargoでプロジェクトを正しくコンパイルするのに使われていますが、望むなら上書きすることも出来ます。

という非常にざっくりとした解説しかありません。そこで、cargo自体のドキュメントを漁って、[Cargo Guide](http://doc.crates.io/guide.html)や[The Manifest Format](http://doc.crates.io/manifest.html)を読むと、もう少し詳しい仕様が分かります。実行可能ファイルを作るプロジェクトの場合には、以下のような仕様であると記述されています：

- プロジェクトが生成する実行ファイルのデフォルトのソースファイルは `src/main.rs` となる。
- さらに、`src/bin/*.rs` が存在し、**なおかつCargo.tomlに`[[bin]]`セクションが一つも存在しない**場合、これらのファイルにそれぞれ対応する実行ファイルが自動的に生成される。
- `Cargo.toml`に`[[bin]]`セクションが一つ以上存在する場合には、各`[[bin]]`セクションの`path`フィールドで明示的に指定された`.rs`ファイルが、その`[[bin]]`セクションのターゲットの実行ファイルのソースファイルとなる。

そこで、まずはcargoソースの`./src/bin`を覗いてみます。すると、次のようにたくさんのソースファイルが含まれていることが分かります:

```shell-session
% ls src/bin
bench.rs              help.rs            package.rs        test.rs
build.rs              init.rs            pkgid.rs          uninstall.rs
cargo.rs              install.rs         publish.rs        update.rs
clean.rs              locate_project.rs  read_manifest.rs  verify_project.rs
doc.rs                login.rs           run.rs            version.rs
fetch.rs              metadata.rs        rustc.rs          yank.rs
generate_lockfile.rs  new.rs             rustdoc.rs
git_checkout.rs       owner.rs           search.rs
```

一方で`Cargo.toml`には、次のような`[[bin]]]`セクションが一つだけ存在します:

```text:Cargo.toml
[[bin]]
name = "cargo"
test = false
doc = false
```

そのため、cargoのプロジェクトでは、`src/bin/`ディレクトリの中にある大量のソースファイル一つ一つに対応する実行可能ファイルが生成されたりは**せず**、一つの実行可能ファイル`cargo`だけが生成されることが分かります。なお、これ以外にも次のようなセクションが存在します:

```text:Cargo.toml
[lib]
name = "cargo"
path = "src/cargo/lib.rs"
```

つまり、このプロジェクトは、cargoという名前のライブラリと、cargoという名前のバイナリを生成することが明示されているということになります。

一方で、ドキュメントからだけでは分からない部分が存在します:

- 疑問1: `[lib]`セクションの方には明示的に`path`フィールドが設定されているが、`[[bin]]`セクションには`path`フィールドが存在しない。この場合には何をソースファイルとするのか。
- 疑問2: `src/bin/`ディレクトリにある大量の`*.rs`ファイルと、実行ファイルの関係はどうなっているのか。

以下では、これらの振る舞いについて探っていくことにします。



## `cargo build`の動作を観察する

まずは、`cargo build`の動作を観察するところから始めます。`cargo build`に`--verbose`オプションを渡すと、実際にrustcコマンドがどのようにして起動されているのかを表示することができます(見やすいように加工してあります):

```shell-session
% cargo clean
% cargo build --locked --frozen --verbose
   Compiling crossbeam v0.2.10

        ...省略... (外部crateのビルドが延々と続く)

   Compiling cargo v0.16.0 (file:///home/tshiozak/gitwork/cargo)

     Running `rustc --crate-name cargo src/cargo/lib.rs --crate-type lib -g -C m
etadata=1cc647335d74a3d0 -C extra-filename=-1cc647335d74a3d0 --out-dir /home/tsh
iozak/gitwork/cargo/target/debug/deps --emit=dep-info,link -L dependency=/home/t
shiozak/gitwork/cargo/target/debug/deps --extern libc=/home/tshiozak/gitwork/car
go/target/debug/deps/liblibc-73c6422e7b19c2f3.rlib --extern glob=/home/tshiozak/
        ...省略... (外部crateに対する--extern指定が続く)
2f37841.rlib -L native=/home/tshiozak/gitwork/cargo/target/debug/build/libgit2-s
ys-f3c0c49dc3037181/out/lib -L native=/usr/lib/x86_64-linux-gnu -L native=/usr/l
ib/x86_64-linux-gnu -L native=/usr/lib/x86_64-linux-gnu -L native=/usr/lib/x86_6
4-linux-gnu -L native=/home/tshiozak/gitwork/cargo/target/debug/build/miniz-sys-
428121ff932ec089/out`

     Running `rustc --crate-name cargo src/bin/cargo.rs --crate-type bin -g -C m
etadata=1681829e50c2a656 -C extra-filename=-1681829e50c2a656 --out-dir /home/tsh
iozak/gitwork/cargo/target/debug/deps --emit=dep-info,link -L dependency=/home/t
shiozak/gitwork/cargo/target/debug/deps --extern libc=/home/tshiozak/gitwork/car
        ...省略... (外部crateに対する--extern指定が続く)
2f37841.rlib --extern cargo=/home/tshiozak/gitwork/cargo/target/debug/deps/libca
rgo-1cc647335d74a3d0.rlib -L native=/home/tshiozak/gitwork/cargo/target/debug/bu
ild/libgit2-sys-f3c0c49dc3037181/out/lib -L native=/usr/lib/x86_64-linux-gnu -L 
native=/usr/lib/x86_64-linux-gnu -L native=/usr/lib/x86_64-linux-gnu -L native=/
usr/lib/x86_64-linux-gnu -L native=/home/tshiozak/gitwork/cargo/target/debug/bui
ld/miniz-sys-428121ff932ec089/out`

    Finished debug [unoptimized + debuginfo] target(s) in 130.99 secs

```

外部crateのビルドを除くと、cargo本体のビルドは、上記2回のrustcの実行ですべてが完結しています。最初のrustc呼び出しで直接コマンドライン引数に指定されているソースファイルは`src/cargo/lib.rs`だけですし、もう一方では`src/bin/cargo.rs`だけが指定されていることが分かると思います。これで、第一の疑問は何となく解消できそうです。つまり、**`path`フィールドが存在しない場合には`src/bin/バイナリ名.rs`というファイルを参照しにゆく**のであろうと推測できます。

一方、第二の疑問、つまり、`src/cargo`や`src/bin`の中にあるその他の`*.rs`ファイルはどのようにして利用されるのかというと、これは[例の本のcrateとモジュールの章](https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/crates-and-modules.html)に書いてある通りです。つまり、Rust言語自体のモジュールの仕組みによって参照されることとなります。

たとえば、`src/cargo/lib.rs`の先頭付近には次のように書いてあります:

```rust:src/cargo/lib.rs
pub mod core;
```

この指定により、rustcは既定のルールによって`src/cargo/core/mod.rs`を読み込むことになります。そして、このようにして読み込んだファイルにmod文があれば、それに従って再帰的にソースファイルが読み込まれていくことになります。

上の`cargo build --verbose`の様子を見る限り、これらの`*.rs`ファイルに対してcargo自身が参照などをしている気配はありませんが、実際には全く参照しないというわけでもありません。しかしながら、「main関数はどこだ」という今回のテーマからは外れるため、これ以上は次回以降のテーマとします。


## ソースを読んで仕様を確認する

というわけで、`path`フィールドが存在しない場合の仕様は何となく推測できたため、本当にそうなっているかをcargoのソースから確認してみましょう。

こういう場合、ソースの先頭から探っていくと効率が悪いため、アタリをつけて探ってみることにします。シンプルなバイナリプロジェクトの場合、`src/main.rs`がデフォルトのソースファイル名となるというのは先ほども述べました。そこで、「main.rs」という文字列でソースツリーを検索してみます:

```shell-session
% grep -r 'main\.rs' src
src/bin/cargo.rs:    run         Build and execute src/main.rs
src/bin/run.rs:Run the main binary of the local package (src/main.rs)
  ...マニュアルページにヒットしてるので省略...
src/cargo/util/toml.rs:        try_add_file(&mut bins, root_path.join("src").join("main.rs"));
src/cargo/util/toml.rs:                Some(s) => s == "main.rs",
src/cargo/util/toml.rs:                       either src/lib.rs, src/main.rs, a [lib] section, or \
src/cargo/util/toml.rs:        let name = if &**bin == Path::new("src/main.rs") ||
src/cargo/util/toml.rs:                      *bin == layout.root.join("src").join("main.rs") {
src/cargo/ops/cargo_new.rs:        Test { proposed_path: format!("src/main.rs"),     handling: H::Bin },
src/cargo/ops/cargo_new.rs:        Test { proposed_path: format!("main.rs"),         handling: H::Bin },
src/cargo/ops/cargo_new.rs:             relative_path: "src/main.rs".to_string(),
src/cargo/ops/cargo_new.rs:            if i.relative_path != "src/main.rs" {
  ...ドキュメントにヒットしてるので省略...
```

この中で明らかに怪しいのは`src/cargo/util/toml.rs`です。そこで、このファイルを覗いてみることにします。

### `Layout::from_project_path()`関数

先ほどのgrepでマッチしていたのは、このファイルの`Layout::from_project_path()`という関数です:

```rust:src/cargo/util/toml.rs(抜粋)

/// Representation of the projects file layout.
///
/// This structure is used to hold references to all project files that are relevant to cargo.

#[derive(Clone)]
pub struct Layout {
    pub root: PathBuf,
    lib: Option<PathBuf>,
    bins: Vec<PathBuf>,
    examples: Vec<PathBuf>,
    tests: Vec<PathBuf>,
    benches: Vec<PathBuf>,
}

impl Layout {
    /// Returns a new `Layout` for a given root path.
    /// The `root_path` represents the directory that contains the `Cargo.toml` file.
    pub fn from_project_path(root_path: &Path) -> Layout {
        let mut lib = None;
        let mut bins = vec![];
        let mut examples = vec![];
        let mut tests = vec![];
        let mut benches = vec![];

        let lib_canidate = root_path.join("src").join("lib.rs");
        if fs::metadata(&lib_canidate).is_ok() {
            lib = Some(lib_canidate);
        }

        try_add_file(&mut bins, root_path.join("src").join("main.rs"));
        try_add_files(&mut bins, root_path.join("src").join("bin"));

        try_add_files(&mut examples, root_path.join("examples"));

        try_add_files(&mut tests, root_path.join("tests"));
        try_add_files(&mut benches, root_path.join("benches"));

        Layout {
            root: root_path.to_path_buf(),
            lib: lib,
            bins: bins,
            examples: examples,
            tests: tests,
            benches: benches,
        }
    }

    fn main(&self) -> Option<&PathBuf> {
        self.bins.iter().find(|p| {
            match p.file_name().and_then(|s| s.to_str()) {
                Some(s) => s == "main.rs",
                None => false
            }
        })
    }
}

fn try_add_file(files: &mut Vec<PathBuf>, file: PathBuf) {
    if fs::metadata(&file).is_ok() {
        files.push(file);
    }
}
fn try_add_files(files: &mut Vec<PathBuf>, root: PathBuf) {
    match fs::read_dir(&root) {
        Ok(new) => {
            files.extend(new.filter_map(|dir| {
                dir.map(|d| d.path()).ok()
            }).filter(|f| {
                f.extension().and_then(|s| s.to_str()) == Some("rs")
            }).filter(|f| {
                // Some unix editors may create "dotfiles" next to original
                // source files while they're being edited, but these files are
                // rarely actually valid Rust source files and sometimes aren't
                // even valid UTF-8. Here we just ignore all of them and require
                // that they are explicitly specified in Cargo.toml if desired.
                f.file_name().and_then(|s| s.to_str()).map(|s| {
                    !s.starts_with('.')
                }).unwrap_or(true)
            }))
        }
        Err(_) => {/* just don't add anything if the directory doesn't exist, etc. */}
    }
}
```

この部分は`Layout`構造体というものの定義と、その生成ルーチンを表しています。`Layout`構造体自体は単純なもので、この構造体に固有の実装はこれですべてです。

まず、補助的なプライベート関数から見ていくと、次のように読めます:

- `try_add_file()`関数は、第二引数で指定されたファイルが存在すれば、これを第一引数に渡されたベクタに追加する。
- `try_add_files()`は、第二引数で指定されたディレクトリの中にあるすべての`*.rs`ファイルを第一引数に渡されたベクタに追加する。

これを踏まえて`Layout::from_project_path()`関数の中を見てみると、新しく`Layout`構造体を生成し、この構造体の各フィールドに対して次のような処理をしています:

- ファイル`src/lib.rs`があれば、これを`lib`フィールドにセットする。
- ファイル`src/main.rs`があれば、これを`bins`ベクタフィールドに追加する。
- ディレクトリ`src/bin`の中にあるすべての`*.rs`ファイルを`bins`ベクタフィールドに追加する。
- ディレクトリ`src/examples`の中にあるすべての`*.rs`ファイルを`examples`ベクタフィールドに追加する。
- ディレクトリ`src/tests`の中にあるすべての`*.rs`ファイルを`tests`ベクタフィールドに追加する。
- ディレクトリ`src/benchs`の中にあるすべての`*.rs`ファイルを`benchs`ベクタフィールドに追加する。

つまり、`Layout::from_project_path()`関数が返す`Layout`構造体のインスタンスの各フィールドが保持しているものは、**`Cargo.toml`に明示的なターゲットセクションが存在しない場合に自動的にプロジェクトのターゲットに対するソースファイルとして選ばれるもの**と一致していることが分かります。

次に、`Layout::from_project_path()`関数を呼び出しているところをgrepで探してみると、`src/cargo/ops/cargo_read_manifest.rs`の`read_manifest()`関数だけがヒットしました。


### `read_manifest()`関数

```rust:src/cargo/ops/cargo_read_manifest.rs(抜粋)
pub fn read_manifest(path: &Path, source_id: &SourceId, config: &Config)
                     -> CargoResult<(EitherManifest, Vec<PathBuf>)> {
    trace!("read_package; path={}; source-id={}", path.display(), source_id);
    let contents = paths::read(path)?;

    let layout = Layout::from_project_path(path.parent().unwrap());
    let root = layout.root.clone();
    util::toml::to_manifest(&contents, source_id, layout, config).chain_error(|| {
        human(format!("failed to parse manifest at `{}`",
                      root.join("Cargo.toml").display()))
    })
}
```

この関数は、

- `path`引数として渡されてきたマニフェストファイル(プロジェクトルートにある`Cargo.toml`)の中身をすべて`contents`変数に文字列の形で読みこむ。
- マニフェストファイルのあるディレクトリをプロジェクトのルートディレクトリとして`Layout::from_project_path()`関数に渡し、Layout構造体のインスタンスを生成する。
- これらを`util::toml::to_manifest()`関数に渡し、実際に使用するマニフェストを得る。

という動作です。返値型など、細かいところで疑問が浮かんできますが、いま知りたい事象に対しては、この詳細はあまり本質的ではないので省きます。[^read_manifest] [^question_op]

[^read_manifest]: 筆者自身は、ソースコードを読んでいる最中に疑問が生じたところは、その都度疑問を解消するべく、タグジャンプやgrepを使って追っかけて、それが何を意味するのかを探っています。この`read_manifest()`関数の解説に対して三行でまとめられているのも、先に`to_manifest()`関数の中や、`read_manifest()`関数を呼び出している側のコードを読んでいるからこそ、こういうスッキリした形で書けるわけです。つまり、この記事は、必ずしも筆者がコードを読んだ通りには書かれていません。まあ、コードの読み方という観点では、むしろ追っかけ方まで詳しく書く方が良いのかもしれませんが、読み物としていかんせん冗長なので、こういう形にしています。

[^question_op]: `let contents = paths::read(path)?;` …… この`?`演算子は例の本(の第一版)には記述がありませんが、[Rust 1.13で追加されたもの](https://blog.rust-lang.org/2016/11/10/Rust-1.13.html#the--operator)です。この演算子は`Result<T,E>`型の値を受け取ることができ、値が`Ok(v)`ならば`v`として評価され、`Err(e)`ならば現在実行中の関数から`return Err(e)`で脱出します。いわゆるシンタックスシュガーというやつです。


### `to_manifest()`関数

`src/cargo/util/toml.rs`に戻って`to_manifest()`関数を読んでみましょう:

```rust:src/cargo/util/toml.rs(抜粋)
pub fn to_manifest(contents: &str,
                   source_id: &SourceId,
                   layout: Layout,
                   config: &Config)
                   -> CargoResult<(EitherManifest, Vec<PathBuf>)> {
    let manifest = layout.root.join("Cargo.toml");
    let manifest = match util::without_prefix(&manifest, config.cwd()) {
        Some(path) => path.to_path_buf(),
        None => manifest.clone(),
    };
    let root = parse(contents, &manifest, config)?;
    let mut d = toml::Decoder::new(toml::Value::Table(root));
    let manifest: TomlManifest = Decodable::decode(&mut d).map_err(|e| {
        human(e.to_string())
    })?;

    return match manifest.to_real_manifest(source_id, &layout, config) {
        Ok((mut manifest, paths)) => {
  ...省略...
            Ok((EitherManifest::Real(manifest), paths))
        }
        Err(e) => {
  ...省略...
        }
    };
  ...省略...
}
```

全部書くと長いので、必要なところだけを抜粋しています。この処理の概要は、

- `parse()`関数で`Cargo.toml`を構文解析し、再帰的なKey-Valueストア構造でこれを格納した`toml::Table`構造体を生成する。[^parse]
- これをRustのシリアライザである`rust_serialize` crate の仕組みを使って`TomlManifest`構造体へとデシリアライズする。
- `TomlManifest::to_real_manifest()`関数を呼び出すことによって、`TomlManifest`構造体の内容を検証しつつ、実際にcargoの他のモジュールから利用しやすい`Manifest`構造体へと変換する。

となっています。いま知りたいことにとって重要なのは、`TomlManifest`構造体から`Manifest`構造体へと変換する`TomlManifest::to_real_manifest()`関数[^to_real_manifest]の処理です。


### `TomlManifest::to_real_manifest()`関数

この関数の中で、`[[bin]]`セクションの処理に相当する部分を抜粋します:

[^parse]: 本題とは外れますが、この`parse()`関数は関数インターフェースも実装もなかなかひどいので、反面教師として読んでみるのもいいかもしれません。こういうkludgeは、実用的なソフトウェアではたまに必要になるものの、この書き方はあまりに場当たり的で感心しないものです。

[^to_real_manifest]: to_real_manifest……ってことは「Real」じゃないマニフェストって何だよ、となりますが、もう一つ、「Virtual」なマニフェストが存在します。本題には関係ないのでVirtualマニフェストについては説明しませんが、`EitherManifest`型を`Real(Manifest)`と`Virtual(VirtualManifest)`の二つのenumとすることにより、`read_manifest()`関数が返す値を自然に表現しています。直和型便利。

```rust:src/cargo/util/toml.rs(抜粋)
impl TomlManifest {
    fn to_real_manifest(&self,
                        source_id: &SourceId,
                        layout: &Layout,
                        config: &Config)
                        -> CargoResult<(Manifest, Vec<PathBuf>)> {
  ...省略...
        let bins = match self.bin {
            Some(ref bins) => {
                let bin = layout.main();

                for target in bins {
                    target.validate_binary_name()?;
                }

                bins.iter().map(|t| {
                    if bin.is_some() && t.path.is_none() {
                        TomlTarget {
                            path: bin.as_ref().map(|&p| PathValue::Path(p.clone())),
                            .. t.clone()
                        }
                    } else {
                        t.clone()
                    }
                }).collect()
            }
            None => inferred_bin_targets(&project.name, layout)
        };
  ...省略...
        // Get targets
        let targets = normalize(&lib,
                                &bins,
                                new_build,
                                &examples,
                                &tests,
                                &benches);
  ...省略...
```

ついでに、このコードから呼ばれている`inferred_bin_targets()`関数と`validate_binary_name()`関数も引用しておきます:

```rust:src/cargo/util/toml.rs(抜粋)
fn inferred_bin_targets(name: &str, layout: &Layout) -> Vec<TomlTarget> {
    layout.bins.iter().filter_map(|bin| {
        let name = if &**bin == Path::new("src/main.rs") ||
                      *bin == layout.root.join("src").join("main.rs") {
            Some(name.to_string())
        } else {
            bin.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
        };

        name.map(|name| {
            TomlTarget {
                name: Some(name),
                path: Some(PathValue::Path(bin.clone())),
                .. TomlTarget::new()
            }
        })
    }).collect()
}
...
    fn validate_binary_name(&self) -> CargoResult<()> {
        match self.name {
            Some(ref name) => {
                if name.trim().is_empty() {
                    Err(human("binary target names cannot be empty.".to_string()))
                } else {
                    Ok(())
                }
            },
            None => Err(human("binary target bin.name is required".to_string()))
        }
    }
...
```

ターゲットセクション、つまり、`[[bin]]`セクションなどのpathフィールドは、二段階で決定されます。上で引用したうちの前半部分が一段目、`normalize()`関数の中の処理が二段目です。

`Layout::from_project_path()`関数の処理を思い出しつつ`to_real_manifest()`関数の一段目の処理を読むと、次のようになっています:

- `[[bin]]`セクションが存在する場合、`match`文の`Some()`に分岐する。
  - 以前にソースツリーを走査した時に`src/main.rs`か`src/bin/main.rs`が存在している場合、`let bin = layout.main()`によってこのファイルパスがbin変数に代入される。[^layout.main]
  - それぞれの`[[bin]]`セクションの内容を走査し、イテレータの`map()`関数で処理する。
      - そのセクションに`path`フィールドがない場合、このセクションの`path`フィールドとして、先ほど見つけた`src/main.rs`か`src/bin/main.rs`のどちらかが使われる。どちらも見つかっていない場合には`None`が代入される。[^bin.main]
      - `path`フィールドがある場合、これがそのまま使われる。
  - なお、`[[bin]]`セクションには`name`フィールドが必須(`validate_binary_name()`関数を参照のこと)。
- 一つも`[[bin]]`セクションが存在しなければ、`inferred_bin_targets()`関数がターゲットセクションを自動生成する:
  - 以前にソースツリーを走査した時に`src/main.rs`が存在していた場合、`name`フィールドがプロジェクト名、`path`フィールドがこの`src/main.rs`となるような`[[bin]]`セクションを生成する。
  - 同じく`src/bin/ファイル名.rs`というようなパス名のソースファイルが存在していた場合、それぞれのソースファイルに対して`name`フィールドが「ファイル名」、`path`フィールドがこの`src/bin/ファイル名.rs`となるような`[[bin]]`セクションを生成する。

[^layout.main]: もし`src/main.rs`と`src/bin/main.rs`の両方が存在していた場合には、`Layout::from_project_path()`関数の中で先にpushされるであろう`src/main.rs`が優先されるように見えます。

[^bin.main]: このケースでは、たとえ`src/bin/<binセクションのnameフィールド値>.rs`というファイルが存在している場合でも、これが使われることはありません。`path`フィールドがNoneではなくなるからです。

一つ注意しないといけないのが、src/main.rsの扱いです。

この段階で、ほとんどのケースでは各`[[bin]]`セクションの`path`フィールドの値が決定されていますが、まだ未決定なケースが一つだけ残っています(`None`が代入されている)。これがまさに今回知りたいケースなのですが、これは最終的に`normalize()`関数の中で解決されることになります。

### `normalize()`関数

というわけで、`normalize()`関数を見てみましょう:

```rust:src/cargo/util/toml.rs(抜粋)
fn normalize(lib: &Option<TomlLibTarget>,
             bins: &[TomlBinTarget],
             custom_build: Option<PathBuf>,
             examples: &[TomlExampleTarget],
             tests: &[TomlTestTarget],
             benches: &[TomlBenchTarget]) -> Vec<Target> {
  ...省略...

    fn bin_targets(dst: &mut Vec<Target>, bins: &[TomlBinTarget],
                   default: &mut FnMut(&TomlBinTarget) -> PathBuf) {
        for bin in bins.iter() {
            let path = bin.path.clone().unwrap_or_else(|| {
                PathValue::Path(default(bin))
            });
            let mut target = Target::bin_target(&bin.name(), &path.to_path());
            configure(bin, &mut target);
            dst.push(target);
        }
    }

  ...省略...

    let mut ret = Vec::new();

    if let Some(ref lib) = *lib {
        lib_target(&mut ret, lib);
        bin_targets(&mut ret, bins,
                    &mut |bin| Path::new("src").join("bin")
                                   .join(&format!("{}.rs", bin.name())));
    } else if bins.len() > 0 {
        bin_targets(&mut ret, bins,
                    &mut |bin| Path::new("src")
                                    .join(&format!("{}.rs", bin.name())));
    }

  ...省略...
```

最終的には`normalize()`関数の中の`bin_targets()`関数で処理されることになります。`bin_targets()`関数の第三引数として渡された関数が、対象となる`[[bin]]`セクションの`path`フィールドがNoneだった場合にデフォルトのソースファイルパスを自動生成するためのクロージャとなっています。これを見ると、libターゲットの有無によって影響を受け[^lib_target]、各`[[bin]]`セクションの`path`フィールドは

- libターゲットが存在する場合、`src/bin/<binセクションのnameフィールド値>.rs`
- libターゲットが存在しない場合、`src/<binセクションのnameフィールド値>.rs`

が設定されることが分かります[^impl_bin]。else if の```bins.len() > 0```というチェックが冗長なのは御愛嬌。

[^lib_target]: ここで`[lib]`セクションではなくlibターゲットと言っているのは、`Cargo.toml`に明示的な`[lib]`セクションが存在しなくても、`src/lib.rs`が存在する場合、これをソースファイルとし、名前をプロジェクト名とする暗黙のlibターゲットが生成されるからです。

[^impl_bin]: なお、このケースではcargo自身がソースファイルの有無をチェックしないため、もしファイルが存在しなければ、rustcがそのまま起動され、そしてエラーを吐くことになります。

以上で、binターゲットについて、すべてのケースを調査することができました。

それにしても、一つのフィールドの値を決定するためのコードが分散していて、どうにもスッキリとしないコードでしたね。



# binターゲットについてのまとめ

ソースを読んだ結果、得られた仕様をまとめてみましょう。

- `Cargo.toml`に`[[bin]]`セクションが一つも存在しない場合<br>
  以下の両方が適用される:
  - `src/main.rs` が存在する場合、次のようなフィールドを持つbinターゲットが生成される:<br>
    `name = "プロジェクト名"`<br>`path = "src/main.rs"`
  - `src/bin/*.rs` というファイルが存在する場合、これらのすべてに対してそれぞれ対応するbinターゲットが生成され、それぞれのフィールドは次のようになる:<br>
    `name = "ファイル名"`<br>`path = "src/bin/ファイル名.rs"`
- `Cargo.toml`に`[[bin]]`セクションが一つ以上存在する場合
  - 上述したような暗黙のターゲットは生成されない。<br>
  - すべての`[[bin]]`セクションに対応するbinターゲットが生成される。<br>
  - 各`[[bin]]`セクションには、ターゲット名を指定するための`name`フィールドが存在している必要がある。<br>
  - `path`フィールドに関しては、以下の規則により決定される:
      - `path`フィールドが存在する場合、これをソースファイルとして使用する。
      - `path`フィールドが存在しない場合には、以下の優先順位により使用するソースファイルが決定される。
          1. `src/main.rs`が存在する場合、これを使用する。
          1. `src/bin/main.rs`が存在する場合、これを使用する。
          1. libターゲットが存在する場合[^lib_target2]、`src/bin/<binセクションのnameフィールド値>.rs`を使用する。
          1. libターゲットが存在しない場合、`src/<binセクションのnameフィールド値>.rs`を使用する。

[^lib_target2]: 暗黙のlibターゲットも含まれるので注意。

さて、実際にこのような仕様で動いているかどうかを確かめるために、[サンプル](https://github.com/AoiMoe/cargo-bin-target)を作成しました。これを実行してみると、次の通りです:

```sh-session:
% ./cargo-bin-target.sh build
...省略...
% ./cargo-bin-target.sh run
run in bin-with-bin-main
  run bin1
    source: src/bin/main.rs
  run bin2
    source: src/bin/bin2.rs
  run bin3
    source: src/bin/main.rs
run in bin-with-main-and-bin-main
  run bin1
    source: src/main.rs
  run bin2
    source: src/bin/bin2.rs
  run bin3
    source: src/main.rs
run in bin-with-main-and-bin-main-and-lib
  run bin1
    source: src/main.rs
  run bin2
    source: src/bin/bin2.rs
  run bin3
    source: src/main.rs
run in bin-without-main
  run bin1
    source: src/bin1.rs
  run bin2
    source: src/bin/bin2.rs
run in bin-without-main-with-lib
  run bin1
    source: src/bin/bin1.rs
  run bin2
    source: src/bin/bin2.rs
run in no-bin
  run bin1
    source: src/bin/bin1.rs
  run bin2
    source: src/bin/bin2.rs
  run no-bin
    source: src/main.rs
```

……これだけ見ても何のことだかわからないかもしれませんが、各プロジェクトの`Cargo.toml`の記述内容と、各バイナリがどのソースから生成されたかをよく観察してみると、確かに上述したような仕様で動いていることが分かると思います。

他のセクションについてはまたの機会ということで。

# `cargo metadata`

さて、目の前にあるcargoプロジェクトの情報を知りたい場合には、`cargo metadata`サブコマンドが便利です。引数を付けずに実行すると、依存関係の解決をして副作用が発生してしまうため、次のように`--no-deps`オプションをつけると良いでしょう:

    cargo metadata --no-deps

出力はJSON形式ですが、このままだと人間には読みにくいため、次のように`jq`コマンドを使うのが便利です[^jq]:

    cargo metadata --no-deps | jq .

[^jq]: Ubuntuの場合、`jq`コマンドはデフォルトではインストールされていないため、`apt-get install jq`とでもしてインストールしてください。

今回のようにbinターゲットについて知りたい場合には、`jq`コマンドのフィルタを書いて限定してもいいでしょう（個人的には`jq . | less`で目視すれば十分な気もしますが）。たとえば、cargoのソースで次のように実行してみましょう:

```sh-session:
% cargo metadata --no-deps | jq '.packages[].targets[] | select(.kind==["bin"])'
{
  "kind": [
    "bin"
  ],
  "name": "cargo",
  "src_path": "src/bin/cargo.rs"
}
```


というわけで、`src/bin/cargo.rs`が目的のファイルです。おわり。

# 脚注