コンテンツにスキップ

RustのCargoとクレート

Rustを始めるのページでは、cargo new と cargo run を使いました。でも、それはほんの入り口です。Cargo は Rust のビルドツールであり、同時にパッケージマネージャーでもあります。コードのコンパイル、テストの実行、ファイルの整形、そしてほかの人が書いたライブラリのダウンロードまでやってくれます。

そうしたライブラリはクレート(crate)と呼ばれます。クレートは、アプリストアに並ぶアプリのようなものだと考えてください。誰かがすでにある問題(JSON の読み込み、Web へのリクエスト、日付の解析など)を解決してくれていて、あなたはコマンドひとつでその解決策を自分のプロジェクトに追加できます。このページでは、その仕組みと、良いクレートの選び方を紹介します。

よく出てくる言葉が2つあります。

  • クレートは、コンパイラがビルドする Rust コードのひとつの単位です。バイナリ(実行できるプログラムで、src/main.rs から始まる)か、ライブラリ(ほかのプログラムから使うためのコードで、src/lib.rs から始まる)のどちらかです。
  • パッケージは、cargo new で作られるものです。Cargo.toml ファイルと、ひとつ以上のクレートが入ったフォルダです。

ふだんの会話では、どちらも「クレート」と呼ぶことがほとんどです。「serde クレートを使おう」と言われたら、それは「crates.io から serde パッケージをプロジェクトに追加しよう」という意味です。

どのパッケージにも Cargo.toml ファイルがあります。これは TOML(英語)というシンプルな設定ファイルの形式で書かれています。cargo new hello_rust を実行すると、次のようなファイルができます。

[package]
name = "hello_rust"
version = "0.1.0"
edition = "2024"
[dependencies]
  • [package] にはプロジェクトの情報を書きます。name と version は、プロジェクトを公開したときにほかの人に見える情報です。
  • edition は、Rust 言語のどのエディションを使うかを選びます。エディション(2018、2021、2024)があるおかげで、Rust は古いコードを壊さずに進化できます。cargo new が書いた値をそのまま使えば大丈夫です。
  • [dependencies] には、コードが使うクレートを並べます。最初は空です。

プロジェクトが大きくなると、ほかのセクションも出てきます。

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
# テストやサンプルだけで使い、本番のプログラムでは使わない
pretty_assertions = "1.4"
[profile.release]
# `cargo build --release` のときの設定
opt-level = 3

cargo add で依存クレートを追加する

Section titled “cargo add で依存クレートを追加する”

[dependencies] は手で編集することもできますが、いちばんかんたんなのは cargo add です。crates.io で最新のバージョンを探して、その行を書き込んでくれます。

Terminal window
cargo add serde_json

クレートによっては、フィーチャー(feature)と呼ばれるオプションの機能を持っています。ビルドを小さく保つため、フィーチャーはデフォルトではオフになっています。オンにするには --features を使います。

Terminal window
cargo add serde --features derive

この2つのコマンドを実行すると、Cargo.toml はだいたい次のようになります。

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"

あとはコードの中でクレートを使うだけです。次に cargo run を実行したときにクレートがダウンロードされ、一度だけコンパイルされて、プログラムに組み込まれます。

use serde_json::json;
fn main() {
let crab = json!({ "legs": 10, "name": "Ferris" });
println!("{}", crab);
println!("名前: {}", crab["name"]);
}

実行結果:

{"legs":10,"name":"Ferris"}
名前: "Ferris"
▶ 実際に試してみよう

"Ferris" にダブルクォートが付いているのは、crab["name"] がふつうの Rust の文字列ではなく、まだ JSON の値だからです。

Rust Playground には人気のクレートが最初から入っているので、cargo add をしなくてもこのコードは動きます。自分のパソコンでは、先に cargo add serde_json を実行する必要があります。このクレートについては、SerdeでJSONを扱うのページでもっとくわしく学びます。

もう必要なくなったクレートを削除するには、cargo remove serde_json を実行します。

serde_json = "1.0" と書いても、ぴったりバージョン 1.0 を求めているわけではありません。「1.0 と互換性のあるバージョンならどれでもいい」という意味です。Cargo はセマンティックバージョニング(SemVer)に従っています。バージョンは MAJOR.MINOR.PATCH の形で、コードを壊すような変更が許されるのはメジャー番号が変わるときだけです。

書き方 Cargo が使ってよい範囲 理由
"1.0"("^1.0" と同じ) >=1.0.0 かつ <2.0.0 1.x のバージョンはすべて互換性があるはず
"1.4.2" >=1.4.2 かつ <2.0.0 1.4.2 以上で、1.x のまま
"0.4" >=0.4.0 かつ <0.5.0 1.0 より前は、マイナー番号の変更が互換性を壊す扱いになる
"~1.4" >=1.4.0 かつ <1.5.0 パッチの更新だけ
"=1.4.2" ちょうど 1.4.2 必要になることはまれ

では、正確なバージョンは誰が決めるのでしょうか。それが Cargo.lock の役目です。最初にビルドしたとき、Cargo は指定に合う最新のバージョンを選び、それを Cargo.lock に書き残します。それ以降のビルドでは、あなたのパソコンでもほかの人のパソコンでも、まったく同じバージョンが使われます。

  • Cargo.lock を手で編集しないこと。 Cargo が管理してくれます。
  • git にコミットすること。 特にプログラム(バイナリ)の場合は大事です。そうすれば、チームメイトや CI でも、あなたとまったく同じものがビルドされます。
  • cargo update を実行すると、Cargo.toml の指定に合う範囲で最新のバージョンに更新し、Cargo.lock を書き換えます。新しいメジャーバージョンに勝手に上がることはありません。メジャーバージョンを上げたいときは、自分で Cargo.toml を書き換えます。

何度も使うことになるサイトが3つあります。

  • crates.io(英語)は公式のレジストリです。公開されているクレートはすべてここにあります。「csv」や「date」のような言葉で検索してみましょう。
  • docs.rs(英語)には、すべてのクレートのドキュメントが自動で生成されて置かれています。たとえば docs.rs/serde_json です。
  • lib.rs(英語)は非公式ですが、より速く使える索引サイトで、クレートをカテゴリ別にまとめています。ざっと見て回るのにぴったりです。

同じ問題を解決するクレートはたくさんあります。では、どう選べばいいのでしょうか。追加する前に、少し時間をとって次の点を確認しましょう。

  1. ダウンロード数。 人気のクレートは何百万回もダウンロードされています。それだけ多くの人が、あなたより先にバグを見つけて直してくれているということです。
  2. 最近の更新。 最新リリースの日付を見てみましょう。何年も更新されていなくても問題ないクレートもあります(単に「完成している」ものもあるからです)。ただ、リポジトリに返事のない issue が山積みになっていないかは確認しておきましょう。
  3. ドキュメント。 docs.rs のページを開いてみましょう。良いクレートは、トップページがわかりやすく、コピーして使えるサンプルが載っています。
  4. バージョンが 1.0 以上。 必須ではありませんが、作者が API の安定を約束しているという目安になります。
  5. 依存が少ないこと。 クレートを追加すると、そのクレートが依存するクレートも一緒についてきます。小さいほうが信頼しやすくなります。

迷ったときは「rust」と自分の課題をセットで検索して、コミュニティで繰り返しおすすめされているクレートを探してみましょう。

全部を覚える必要はありません。この表を手元に置いておけば、使っているうちに自然と覚えます。

コマンド 何をするか
cargo new my_app 新しいバイナリプロジェクトを作る(ライブラリなら --lib を付ける)
cargo build target/debug/ にコンパイルする
cargo run コンパイルして実行する(引数は -- のあとに渡す:cargo run -- input.txt)
cargo check プログラムをビルドせずにエラーをチェックする。build よりずっと速い
cargo test テストを実行する(Rustのテストを参照)
cargo fmt すべてのコードを標準のスタイルに整形する
cargo clippy リンターを実行する。よりシンプルで Rust らしい書き方を提案してくれる
cargo doc --open プロジェクトとすべての依存クレートの HTML ドキュメントを作って開く
cargo update Cargo.lock を互換性のある最新バージョンに更新する
cargo build --release 最適化したプログラムを target/release/ にコンパイルする

おすすめの習慣は、書いている途中でこまめに cargo check を実行し、コミットの前に cargo fmt と cargo clippy を実行することです。cargo doc --open も隠れた名機能です。自分が使っているのとまったく同じバージョンのクレートのドキュメントが読めて、オフラインでも使えます。

デバッグビルドとリリースビルド

Section titled “デバッグビルドとリリースビルド”

Cargo はデフォルトでデバッグビルドを作ります。コンパイルが速く、整数がオーバーフローしたらパニックするなど、安全のためのチェックが追加で入っています。開発中にはぴったりです。

リリースビルド(cargo build --release や cargo run --release)は、コンパイルに時間がかかる代わりに、実行がずっと速くなります。10倍以上速くなることもよくあります。プログラムを配布するときや、速度を測るときに使いましょう。

コードの中から、どちらのビルドなのかを知ることもできます。

fn main() {
if cfg!(debug_assertions) {
println!("デバッグビルド:追加のチェックが有効です");
} else {
println!("リリースビルド:速度重視で最適化されています");
}
}

実行結果:

デバッグビルド:追加のチェックが有効です
▶ 実際に試してみよう

Playground では、メニューで「Debug」から「Release」に切り替えてもう一度実行すると、もう一方の行が表示されます。

バイナリクレートとライブラリクレートを一緒に使う

Section titled “バイナリクレートとライブラリクレートを一緒に使う”

ひとつのパッケージに、ライブラリとバイナリを同時に入れることができます。これはとてもよくある構成です。本当のロジックは src/lib.rs に置き、src/main.rs は小さく保って、ライブラリを呼び出すだけにします。

greeter/
├── Cargo.toml
└── src/
├── lib.rs # ライブラリクレート:再利用できるロジック
└── main.rs # バイナリクレート:実行するプログラム
src/lib.rs
pub fn greet(name: &str) -> String {
format!("こんにちは、{name}さん!")
}
src/main.rs
fn main() {
// ライブラリの名前はパッケージ名と同じになる
println!("{}", greeter::greet("Ferris"));
}

なぜこのように分けるのでしょうか。lib.rs のコードはテストしやすく、ほかのプログラム(や2つ目のバイナリ)から再利用もできるからです。Playground はひとつのファイルしか使えませんが、Rustのモジュールのページのように pub mod を使えば、同じ感覚を試せます。

mod greeter {
pub fn greet(name: &str) -> String {
format!("こんにちは、{name}さん!")
}
}
fn main() {
println!("{}", greeter::greet("Ferris"));
}

実行結果:

こんにちは、Ferrisさん!
▶ 実際に試してみよう

ワークスペースをちょっとだけ

Section titled “ワークスペースをちょっとだけ”

プロジェクトが大きくなり、まとめて扱いたいパッケージが複数できたら(たとえば、コマンドラインアプリ、Web サーバー、共通のライブラリなど)、それらをひとつのワークスペース(workspace)に入れることができます。ワークスペースの中では Cargo.lock と target/ フォルダがひとつにまとめられるので、すべてが一緒にビルドされ、同じバージョンの依存クレートが使われます。

いちばん上の Cargo.toml には、メンバーを並べます。

[workspace]
members = ["app", "core"]
resolver = "3"
my_project/
├── Cargo.toml # 上のワークスペース用ファイル
├── Cargo.lock # すべてのメンバーで共有
├── app/ # バイナリパッケージ
└── core/ # ライブラリパッケージ

いちばん上で cargo build を実行するとすべてがビルドされ、cargo run -p app でひとつのメンバーだけを実行できます。最初のうちのプロジェクトでは必要ありませんが、ほかの人のコードを読むときに見分けられると役に立ちます。

クレートを追加せずに使ってしまう。 use serde_json::json; と書いたのに cargo add serde_json を忘れていると、コンパイラはクレートを見つけられません。

error[E0432]: unresolved import `serde_json`
--> src/main.rs:1:5
|
1 | use serde_json::json;
| ^^^^^^^^^^ use of unresolved module or unlinked crate `serde_json`
|
= help: you might be missing a crate named `serde_json`

これは「serde_json をインポートできません。serde_json という名前のクレートが足りないのかもしれません」という意味です。cargo add serde_json を実行すれば直ります。つづりも確認しましょう。クレート名には - や _ が使われますが、コードの中では - は必ず _ になります。

フィーチャーを忘れる。 #[derive(Serialize)] で derive マクロが見つからないと言われたら、おそらく --features derive を付けずに serde を追加しています。もう一度 cargo add serde --features derive を実行してください。既存の行が更新されます。

デバッグビルドで速度を測る。 プログラムが遅いと感じたら、コードを変える前に cargo run --release を試してみましょう。

serde_json の json! マクロを使って、3つのキーを持つクレートの情報を表してください。キーは downloads(数値)、name、version です。値全体を表示してから、バージョンだけを表示します。

ヒント:cargo add で依存クレートを追加するの crab の例と同じ形で書けます。手元のマシンでは先に cargo add serde_json を実行してください。Playgroundには最初から入っています。

解答を見る
use serde_json::json;
fn main() {
let krate = json!({
"downloads": 500,
"name": "serde_json",
"version": "1.0"
});
println!("{}", krate);
println!("バージョン: {}", krate["version"]);
}

実行結果:

{"downloads":500,"name":"serde_json","version":"1.0"}
バージョン: "1.0"
▶ 実際に試してみよう

変数名が krate なのは、crate がRustの予約語だからです。このつづりは、Rustのコードでよく見かけるちょっとしたジョークです。さっきと同じく、バージョンにダブルクォートが付いているのは、まだJSONの値だからです。ふつうのテキストとして取り出す方法は、SerdeでJSONを扱うのページで紹介しています。

2. デバッグビルドでだけ表示するメッセージ

Section titled “2. デバッグビルドでだけ表示するメッセージ”

メッセージの前に [debug] を付けて表示する関数 debug_log(msg: &str) を書いてください。ただし、表示するのはデバッグビルドのときだけです。main でちょっとした計算の前後にこれを使い、結果はふつうに表示します。

ヒント:デバッグビルドとリリースビルドと同じように、println! を if cfg!(debug_assertions) { ... } で囲みます。

解答を見る
fn debug_log(msg: &str) {
if cfg!(debug_assertions) {
println!("[debug] {msg}");
}
}
fn main() {
debug_log("計算を始めます");
let mut total = 0;
for n in 1..=10 {
total += n;
}
debug_log("計算が終わりました");
println!("1から10までの合計は{total}です");
}

実行結果:

[debug] 計算を始めます
[debug] 計算が終わりました
1から10までの合計は55です
▶ 実際に試してみよう

Playgroundを「Release」に切り替えて(手元なら cargo run --release で)もう一度実行してみてください。最後の行だけが表示されます。本格的なプロジェクトでは、ログ出力のページで紹介しているクレートを使うと、debug や info のようなレベル付きで、きちんとこれを実現できます。

  • Cargo はコードのビルド、実行、テスト、整形をしてくれる。さらに crates.io からクレートをダウンロードしてくれる。
  • Cargo.toml は「何がほしいか」、Cargo.lock は「実際に使った正確なバージョン」の記録。どちらもコミットしよう。
  • cargo add name --features x で依存クレートを追加する。"1.0" は「互換性のある 1.x ならどれでも」という意味。
  • クレートは、ダウンロード数、最近の更新、docs.rs のドキュメントの充実度で判断しよう。
  • 書いている途中は cargo check、コミット前は cargo fmt と cargo clippy、速度が必要なら --release。
  • 再利用できるロジックは src/lib.rs に置き、src/main.rs は小さく保とう。

理解度チェック

1. Cargo.toml で serde_json = "1.0" と書くと、どういう意味になりますか?

2. ビルドで使われたすべての依存クレートの正確なバージョンを記録しているファイルはどれですか?

3. プログラムは作らずに、できるだけ速くコードのエラーをチェックしたいとき、いちばん合うコマンドはどれですか?

4. derive フィーチャーをオンにして serde を追加するには、どうしますか?

スコア: 0 / 4