Rustのツールチェーンが特別な理由

多くのプログラミング言語では,開発に必要なツールが分散している.C/C++では,ビルドシステム(Make, CMake, Ninja, Meson, ...),パッケージマネージャ(Conan, vcpkg, ...),フォーマッタ(clang-format),リンター(clang-tidy, cppcheck)がそれぞれ独立したプロジェクトとして存在し,プロジェクトごとにどのツールを採用するかが異なる.JavaScriptでも,パッケージマネージャだけでnpm, yarn, pnpm, bunが乱立し,ビルドツールはwebpack, Vite, esbuild, Rollup, Turbopackと選択肢が膨大になる.

Rustはこの問題に対して明確な答えを持っている: Cargoという単一のツールに,開発に必要なほぼすべての機能を統合した.

Cargo: 単なるビルドツールではない

Bitterless RustではCargoを「ビルドツール兼パッケージマネージャ」と紹介した.これは正しいが,Cargoの全貌からすると氷山の一角にすぎない.

Cargoが担う役割を列挙すると:

コマンド 役割
cargo new / cargo init プロジェクトの作成
cargo build ビルド
cargo run ビルド + 実行
cargo check 型チェック(バイナリ生成なし)
cargo test テストの実行
cargo bench ベンチマークの実行
cargo doc ドキュメント生成
cargo fmt コードフォーマット
cargo clippy リント(静的解析)
cargo add / cargo remove 依存関係の管理
cargo publish crates.ioへのパッケージ公開
cargo install バイナリクレートのインストール
cargo update 依存関係のアップデート
cargo tree 依存関係ツリーの表示
cargo fix コンパイラの提案する修正の自動適用

これらが すべて一つのツールに統合されている.プロジェクトの作成からテスト,ドキュメント生成,パッケージ公開まで,cargoコマンドだけで完結する.

この統一性がもたらす恩恵

統一されたツールチェーンの恩恵は,単に「覚えることが少ない」だけではない.

すべてのRustプロジェクトが同じ構造を持つ. Cargo.tomlを見ればプロジェクトの依存関係がわかり,src/以下にソースコードがあり,cargo testでテストが動く.GitHubで見つけた任意のRustプロジェクトに対して,この前提が成り立つ.

これはC/C++の世界では考えられないことだ.CMakeを使うプロジェクト,Mesonを使うプロジェクト,Autotoolsを使うプロジェクト — ビルド方法を理解するだけでも一苦労する.

cargo test: 統合されたテスト

Rustのテストは,外部のテストフレームワークを導入することなく書ける.ソースコード内に直接テストを書く:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(1, 2), 3);
    }

    #[test]
    fn test_add_negative() {
        assert_eq!(add(-1, 1), 0);
    }
}

#[cfg(test)]は条件付きコンパイル属性で,テスト実行時にのみこのモジュールがコンパイルされることを意味する.#[test]属性がついた関数がテストケースとして認識される.

cargo test
running 2 tests
test tests::test_add ... ok
test tests::test_add_negative ... ok

test result: ok. 2 passed; 0 failed; 0 ignored

テストが言語とツールチェーンに統合されていることで,すべてのRustライブラリが同じ方法でテストを実行できるcargo testだけでよい.

cargo bench: ベンチマーク

パフォーマンス計測もCargoに統合されている.nightly Rustでは標準のベンチマーク機能が使え,stableではcriterionクレートを使うのが一般的だ:

// benches/my_benchmark.rs (criterion 使用)
use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn fibonacci(n: u64) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
cargo bench

プロジェクトのベンチマークがcargo bench一発で実行できる.

rustfmt: 統一されたフォーマット

rustfmtはRustの公式フォーマッタであり,cargo fmtで実行できる:

cargo fmt

重要なのは,rustfmtが 公式 であり,Rustコミュニティにおいて 事実上の標準 になっている点だ.

Go言語がgofmtでフォーマット戦争を終結させたのと同様に,Rustではrustfmtがコードスタイルの議論を終わらせた.インデントはスペース4つ,波括弧の位置はこう,useの並び順はこう — これらはプロジェクトごとに議論する必要がない.

rustfmt.tomlによるカスタマイズも可能だが,多くのプロジェクトではデフォルト設定がそのまま使われている.

Clippy: コンパイラを超えるリント

ClippyはRustの公式リンターで,cargo clippyで実行できる:

cargo clippy

Clippyが特に優れているのは,コンパイラが検出しない問題 を指摘してくれる点だ.Rustのコンパイラは非常に厳格で,型安全性やメモリ安全性に関する多くの問題をコンパイル時に検出するが,Clippyはさらにその上をいく.

Clippyが検出するものの例:

イディオマティックでないコード

// Clippy が警告するコード
if x == true {
    // ...
}

// Clippy の提案
if x {
    // ...
}

パフォーマンス上の問題

// Clippy が警告: 不要な clone
let s = some_string.clone();
println!("{}", s);
// → clone せず参照を使える場面で clone している

// Clippy が警告: collect してからの len
let count = v.iter().filter(|x| x > 0).collect::<Vec<_>>().len();
// → .count() を使うべき
let count = v.iter().filter(|x| x > 0).count();

潜在的なバグ

// Clippy が警告: 浮動小数点の等値比較
if x == 0.0 {
    // ...
}

// Clippy の提案: epsilon を使った比較
if x.abs() < f64::EPSILON {
    // ...
}

Clippyのリントは2025年時点で700以上存在し,以下のカテゴリに分類されている:

カテゴリ 説明
clippy::correctness ほぼ確実にバグであるコード
clippy::suspicious 怪しいがバグとは限らないコード
clippy::style イディオマティックでないコード
clippy::complexity 不必要に複雑なコード
clippy::perf パフォーマンスが改善できるコード
clippy::pedantic より厳格なリント
clippy::nursery 実験的なリント

CIにcargo clippy -- -D warningsを入れることで,Clippyの警告をエラーとして扱い,コード品質を継続的に保つことができる.

cargo doc: ドキュメント生成

ドキュメント生成についてはドキュメンテーションの章で詳しく扱うが,ここでは「Cargoに統合されている」という点だけ触れておく.

cargo doc --open

このコマンドで,プロジェクト内のすべての公開APIのドキュメントがHTMLとして生成され,ブラウザで開かれる.依存クレートのドキュメントも一緒に生成されるため,オフラインでもすべてのドキュメントを参照できる.

rustup: ツールチェーンのバージョン管理

Cargoの外側にはrustupがある.これはRustツールチェーン自体のバージョンマネージャだ:

# stable/beta/nightly の切り替え
rustup default stable
rustup default nightly

# クロスコンパイル用ターゲットの追加
rustup target add wasm32-unknown-unknown
rustup target add aarch64-unknown-linux-gnu

# コンポーネントの追加
rustup component add clippy
rustup component add rustfmt
rustup component add rust-analyzer

rustupが管理するのは:

  • Rustコンパイラ(rustc)のバージョン

  • Cargoのバージョン

  • 標準ライブラリ のバージョン

  • ターゲットプラットフォーム のサポート

  • コンポーネント (clippy, rustfmt, rust-analyzer, etc.)

Node.jsでいうnvmとnpmを統合して,さらにクロスコンパイル用のツールチェーンまで管理できるようにしたものと考えるとわかりやすい.

rust-analyzer: IDEサポート

rust-analyzerはRustの公式Language Server Protocol (LSP)実装であり,rustup component add rust-analyzerでインストールできる.

LSPサーバーが公式に提供されていることで,VS Code, Neovim, Emacs, IntelliJ — どのエディタを使っていても,同等の補完,型表示,リファクタリング支援が受けられる.

まとめ: 「一つのやり方」の価値

Rustのツールチェーンの素晴らしさは,個々のツールの品質だけでなく,エコシステム全体が統一された方法で動作する という点にある.

任意のRustプロジェクトに対して:

  • cargo buildでビルドできる

  • cargo testでテストを実行できる

  • cargo clippyでリントをかけられる

  • cargo fmtでフォーマットできる

  • cargo docでドキュメントを生成できる

この統一性は,個人の開発体験だけでなく,チーム開発やOSSの保守においても絶大な効果を発揮する.新しいプロジェクトに参加したとき,ビルドシステムの使い方を学ぶ必要がない.CIの設定が予測可能になる.コードレビューでスタイルの議論が不要になる.

これはRustの設計思想 — 正しいやり方を一つ提供し,それをデフォルトにする — の最も成功した実践例の一つだ.