// dictionary
Cargo.
Cargoは、Rustの公式パッケージマネージャです。パッケージの依存関係を宣言できるようにし、いつでも同じ結果になるビルドを保証します1。さらに、依存を取得したうえで正しい引数を組み立ててrustcを呼び出すところまで引き受けるため、ビルドシステムとしての役割も兼ねています1。
rustcにソースファイルを渡すだけでもコンパイルはできます。しかしコンパイラフラグや外部の依存を指定し始めるとコマンドは一気に複雑になり、依存のそのまた依存まで含めて正しいバージョンを手で揃え続けるのは骨が折れるうえに間違いも起きます1。CargoはこれをCargo.tomlへの宣言に置き換え、以降はcargoのサブコマンド1つで済ませられるようにします。
$ cargo new shopping # パッケージのひな形を作る
Creating binary (application) `shopping` package
$ cd shopping
$ cargo add regex # 依存を Cargo.toml に追記する
Updating crates.io index
Adding regex v1.13.1 to dependencies
Locking 5 packages to latest Rust 1.93.0 compatible versions
$ cargo run # 依存ごとビルドして実行する
Compiling memchr v2.8.3
Compiling regex-syntax v0.8.11
Compiling aho-corasick v1.1.5
Compiling regex-automata v0.4.18
Compiling regex v1.13.1
Compiling shopping v0.1.0 (/path/to/shopping)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.34s
Running `target/debug/shopping`
Hello, world!
指定した依存はregexひとつですが、解決されたパッケージは5つあります。regexが内部で使っているパッケージまでCargoが辿って取得し、それぞれのクレートをビルドしているためで、Locking 5 packagesはその解決結果です(Rust 1.93時点の実行結果。cargo addが出力する情報行の一部は省略しています)。
rustcとの関係
コンパイルそのものを行うのはあくまでrustcで、Cargoはその司令塔にあたります。rustc main.rsと実行すればCargoなしでも実行ファイルは作れますが、ファイルが増え、外部の依存が絡み、同じビルドを再現したくなった時点でCargoが要るようになります。
代表的なコマンド
よく使うサブコマンドは次のとおりです2。
| コマンド | 何をするか |
|---|---|
cargo new 名前 |
パッケージのひな形(Cargo.tomlとsrc/main.rs)を作る |
cargo build |
依存ごとビルドし、実行ファイルをtarget/debug/に置く |
cargo run |
ビルドしてそのまま実行する |
cargo check |
コード生成を省いてエラーの有無だけを調べる。cargo buildより速い |
cargo test |
パッケージ内のテストをコンパイルして実行する |
cargo add 名前 |
依存をCargo.tomlの[dependencies]へ追記する |
Cargo.tomlとCargo.lockの役割分担
2つのメタデータファイルは、Cargo.tomlが人の書く「要望」、Cargo.lockがCargoの書き出す「結果」という関係にあります3。
Cargo.toml(マニフェスト) |
Cargo.lock(ロックファイル) |
|
|---|---|---|
| 書く人 | 自分で書く | Cargoが生成・更新する(手で編集しない) |
| 書かれる内容 | 依存を大まかな範囲で指定する(regex = "1") |
実際に使われた版とチェックサムを正確に記録する |
| 目的 | 何が必要かを宣言する | ビルドを再現できるようにする |
Cargo.tomlを書き換えない限り、CargoはCargo.lockに記録された版をそのまま使います。そのため時期や環境が違っても同じ依存でビルドできます4。依存を足したりcargo updateを実行したりすれば、その部分は解決し直されてCargo.lockが更新されます3。
crates.ioとの関係
crates.ioはRustコミュニティのパッケージレジストリで、Cargoの既定のレジストリです5。[dependencies]に名前とバージョンを書いて外部パッケージを宣言すると、Cargoは既定でここから該当パッケージを取得します。cargo addが最初に「Updating crates.io index」と表示するのは、どのバージョンが存在するかをこのレジストリに問い合わせているためです。
なお標準ライブラリはツールチェーンの一部(rustupではrust-stdコンポーネント)として配布されるため、crates.ioからの取得対象にはなりません6。
補足
Cargoが引き受けている4つのこと
The Cargo Bookは、Cargoの仕事を次の4つとして挙げています1。
- パッケージの情報を持つ2つのメタデータファイルを導入する
- パッケージの依存を取得してビルドする
- 正しい引数を組み立てて
rustc(または別のビルドツール)を呼び出す - パッケージを扱いやすくするための規約を導入する
1番目の2つのファイルがCargo.tomlとCargo.lockです3。4番目の規約は、src/main.rsなら実行ファイル・src/lib.rsならライブラリといった、クレートの項で扱うファイル配置のルールを指します。
cargo checkが速い理由
cargo checkは、コンパイルのうち最後のコード生成(機械語の出力)を行いません。そのぶんcargo buildより速く終わり、型エラーや借用チェッカーが出すエラーを見つける用途に向いています7。
ただし、コード生成の段階でしか出ない診断やエラーはcargo checkでは報告されません7。最終的な確認はcargo buildやcargo testで行う必要があります。
Cargo.lockをバージョン管理に入れるか
cargo newは既定でCargo.lockを追跡する設定にしますが、実際に入れるかどうかはパッケージの事情によります4。バージョン管理に入れておくと、git bisectで不具合の原因をたどるときや、CIの失敗が新しいコミットのせいだと切り分けたいときに役立ちます4。
一方でCargo.lockは、自分のパッケージを使う側には影響しません。利用者の依存解決に効くのはCargo.tomlだけです4。例外はcargo install --lockedで、このときだけはロックファイルに記録された版が使われます4。
なおcargo newが生成する.gitignoreはビルド成果物の/targetだけを無視する内容なので、Cargo.lockは既定でコミット対象に含まれます(Rust 1.93時点)2。
Footnotes
この辞書が使われているページ
backlinks 6