Rustのビルド待ち時間を削る実践レシピ。高速リンカとsccache、cargo-nextestで開発サイクルを加速させる

Rustのビルド待ち時間を削る実践レシピ。高速リンカとsccache、cargo-nextestで開発サイクルを加速させる

2026/10/01
目次
開閉
ビルド待ち時間を短縮して軽快に開発を進める様子

Webアプリケーションのパフォーマンス改善や、社内向けの高速なCLIツール作成などで、実務にRustを取り入れる現場が増えてきました。メモリ安全性と圧倒的なランタイム速度は頼もしい限りですが、日常的にコードを書く中でどうしても向き合わなければならないのが「ビルド待ち時間」の存在です。

コードを数行修正して cargo test や cargo run を叩いたとき、ターミナルで十数秒から数十秒待たされる。このわずかな待ち時間が、開発のリズムや集中力を少しずつ削ぎ落としていきます。

Rustのコンパイルが重くなる要因は、厳格な型推論や借用チェック、マクロ展開だけではありません。実は実務で手を動かしているとき、時間の多くを奪っているのは 「最終成果物を作るリンク処理」 と 「依存クレートの再コンパイル」、そして 「逐次的なテスト実行」 です。

今回は、既存のソースコードには一切手を加えず、手元の環境設定とツールの導入だけでインクリメンタルビルドとテストを大幅に軽快にする「3つのアプローチ」をまとめました。


1. ボトルネックの正体:なぜコード修正後のビルドは遅いのか

Rustで開発していて最も頻度が高いのは、「関数の中身を1〜2行書き換えて動作を確かめる」というインクリメンタルな作業です。

このとき、cargo check(型チェックと構文解析)だけであれば数秒で終わることが多いのですが、実際にバイナリを生成したりテストを実行したりしようとすると、途端に時間が伸びます。

その大きな要因が リンク(Linking) です。

Rustは静的リンクを基本とし、依存している膨大なライブラリやランタイムを単一のバイナリに結合します。標準のリンカ(Linuxの GNU ld や macOSの旧リンカ、Windowsの link.exe)は堅牢ですが、大規模なオブジェクトファイルを扱う際の並列化が弱く、コードを1行変えただけでも数十秒のリンク待ちが発生してしまうのです。

この「リンク待ち」と「テスト実行の待ち時間」を解消するために、以下の3段構えで開発環境を整えていきます。

  1. 高速リンカへの差し替え: リンク処理を並列化して数秒以内に終わらせる
  2. sccacheの導入: 依存クレートのビルド結果をローカル全体でキャッシュする
  3. cargo-nextestの導入: テストをプロセス単位で最適に並列実行する

2. 第1の手:高速リンカへの差し替え(OS別の最適解)

最も劇的な効果を実感しやすいのが、リンカの差し替えです。ただし、お使いのOSによって最適な選択肢が大きく異なります。ここを混同すると「ツールを入れたのに動かない」「不要な巨大パッケージを入れてしまった」という混乱に繋がりやすいため、まずはOS別の推奨アプローチを整理しておきます。

OS環境推奨アプローチ理由
Linux (Ubuntu / Arch等)mold に差し替え圧倒的に高速(GNU ldの数倍〜十数倍)。現在最も効果が高い
Windows (MSVC)lld-link に差し替えLLVMの高速リンカ。MSVCの link.exe に比べて大幅に短縮
macOS (Apple Silicon)OS標準(Xcode 15+)のままXcode 15以降で新リンカが導入され高速化済み。追加ツール不要
Lowom 編集長 編集長
TIPS macOS環境でのリンカ事情
オープンソースのmoldはLinux(ELF)専用であり、macOS(Mach-O)には対応していません(かつて作者の植山氏がmacOS向け商用リンカ「sold」を提供していましたが、現在は開発終了となっています)。なお、Apple Silicon MacではXcode 15以降で標準リンカ自体がゼロから再設計され大幅に高速化されたため、macOS環境では追加ツールを入れず標準リンカのままで十分に快適な速度が得られます。

リンカのインストール

LinuxおよびWindows環境で必要なツールをインストールします(※macOS環境の方は追加インストール不要です)。

# Ubuntu / Debian (Linux)
sudo apt update && sudo apt install -y mold clang

# Arch Linux
sudo pacman -S mold clang

# Windows (PowerShell / Scoop経由でLLVMを導入)
scoop install llvm
# ※winget をお使いの場合は: winget install LLVM.LLVM

~/.cargo/config.toml でのグローバル設定

プロジェクト個別の Cargo.toml を汚さず、マシン全体で高速リンカを有効にするため、ホームディレクトリ配下の ~/.cargo/config.toml(Windowsの場合は %USERPROFILE%\.cargo\config.toml)に設定を記述します。

# Linux (x86_64) 向け: mold を使用
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

# Linux (ARM64 / aarch64) 向け: mold を使用
[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

# Windows (x86_64 MSVC) 向け: lld-link を使用
[target.x86_64-pc-windows-msvc]
linker = "lld-link"

# ※macOS (Apple Silicon) は Xcode 15以降の標準リンカで十分高速なため、設定不要です

手元での差分ビルド検証

効果を客観的に確認するため、中規模なWeb APIプロジェクト(REST APIサーバー、依存クレート約120個、総コード行数約1.5万行)を対象に、コントローラー層の処理を1行変更した際のインクリメンタルビルド時間を計測しました。

項目検証環境のスペック
OSUbuntu 24.04 LTS (x86_64) / Linux 6.8
CPUAMD Ryzen 7 7840HS (8コア / 16スレッド)
メモリ32 GB DDR5
Rustrustc 1.81.0 (stable)
リンカGNU ld 2.42 vs mold 2.32.0
# 標準リンカ(GNU ld)でのビルド
$ touch src/main.rs && time cargo build
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.42s
real    0m9.452s

# mold 適用後でのビルド
$ touch src/main.rs && time cargo build
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.83s
real    0m1.861s

9秒以上かかっていたインクリメンタルビルドが 約1.8秒 にまで短縮されました。1回あたりの差は7秒程度ですが、1日に何十回もコンパイルを繰り返す現場では、集中力の維持において決定的な違いを生みます。


3. 第2の手:sccache によるコンパイルキャッシュの共有

ブランチを切り替えたり、cargo clean を叩いたりした際、tokio や serde、axum といった巨大な依存クレート群をゼロから再コンパイルさせられてうんざりした経験はないでしょうか。

Mozillaが開発している sccache(Shared Compilation Cache)は、C/C++の ccache に似た仕組みで、コンパイルされた中間アーティファクトをハッシュ値とともに保存・再利用してくれます。

sccache の導入手順

Rustのツールチェイン管理には cargo-binstall を使うと、コンパイル待ちなしで事前ビルド済みバイナリを即座に導入できます。

# cargo-binstall 経由でバイナリを高速インストール
cargo binstall sccache

# または通常の cargo install
cargo install sccache --locked

インストール後、こちらも ~/.cargo/config.toml に rustc-wrapper として指定します。

[build]
rustc-wrapper = "sccache"

キャッシュの動作と統計確認

設定後に cargo build を実行すると、バックグラウンドで sccache デーモンが立ち上がり、コンパイル結果を自動的にキャッシュストレージ(デフォルトはローカルディスク)に蓄積します。

キャッシュの効き具合は sccache --show-stats でいつでも確認できます。

$ sccache --show-stats
Compile requests                   342
Compile requests executed           28
Cache hits                         314
Cache misses                         0
Cache timeouts                       0
Cache errors                         0
Cache hit rate                   91.81 %
Supported/executed compiles        100 %
Non-cacheable compiles               0
Cache size                         1.2 GiB
Max cache size                    10.0 GiB

キャッシュヒット率が90%を超えていれば、別ブランチへの移動や一時的な作業ディレクトリの作成時でも、重い依存クレートの再コンパイルがほぼ瞬時にスキップされます。

Lowom 編集長 編集長
CAUTION ディスク容量の肥大化に注意
sccacheのデフォルトキャッシュ上限は通常10GBに設定されています。SSDの容量を圧迫しないよう、必要に応じて環境変数 `SCCACHE_DIR` や `SCCACHE_CACHE_SIZE="5G"` などで上限サイズを指定しておくと安心です。

4. 第3の手:cargo-nextest でテスト実行をプロセス並列化する

コード修正後のリンクとキャッシュが整ったら、最後のボトルネックである「テストの実行速度」を改善します。

Rust標準の cargo test は、1つのテストバイナリ内でマルチスレッドを使ってテスト関数を実行します。しかし、この方式には実務上の課題がいくつかあります。

  • テスト間でグローバル状態(環境変数や一時ファイル、DB接続など)を共有していると、スレッド競合でテストが不安定になりやすい
  • 途中でハングしたテスト関数があると、全体の進行が止まり原因特定が難しい
  • 出力ログが混ざり合い、どのテストが失敗したのか追うのにスクロールが必要になる

Meta社が開発・オープンソース化した cargo-nextest は、これらの課題を根本から解決する次世代テストランナーです。

nextest の導入と実行

こちらもバイナリインストールが可能です。

cargo binstall cargo-nextest

使い方はきわめてシンプルで、普段の cargo test を cargo nextest run に置き換えるだけです。

$ cargo nextest run
   Compiling my-api-service v0.1.0 (/path/to/project)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.42s
    Starting 28 tests across 3 binaries
        PASS [   0.008s] my-api-service::models user_deserialization_test
        PASS [   0.012s] my-api-service::handlers health_check_returns_200
        PASS [   0.015s] my-api-service::auth token_validation_success
        PASS [   0.021s] my-api-service::db connection_pool_acquire
        PASS [   0.034s] my-api-service::api create_user_endpoint
...
------------
     Summary [   0.142s] 28 tests run: 28 passed, 0 skipped

なぜ nextest は速く、壊れにくいのか

cargo-nextest の最大の特徴は、「各テストを独立したプロセスとして分離し、マシンコアを使い切って並列実行する」 点にあります。

  • スレッドセーフティの心配が不要: 各テストが別プロセスで動くため、環境変数の書き換え(std::env::set_var)や静的変数の操作が他のテストに干渉しません。
  • きめ細かいタイムアウト検知: テストごとに「スローテスト判定(例: 5秒以上)」や「タイムアウトによる強制停止」を設定できるため、デッドロックでCIが30分止まるような事故を未然に防げます。
  • 失敗箇所の明瞭なサマリー: 失敗したテストの標準出力・エラートレースだけが末尾に整理されて表示されるため、大量のログからエラーを探す認知負荷がゼロになります。

5. コピペで使える ~/.cargo/config.toml まとめ

ここまで紹介した高速リンカと sccache の設定を1箇所にまとめた設定例です。

ホームディレクトリ(~/.cargo/config.toml または %USERPROFILE%\.cargo\config.toml)に配置するだけで、手元のすべてのRustプロジェクトで自動的に高速化の恩恵を受けられます。

# -------------------------------------------------------------
# ~/.cargo/config.toml - ローカル開発用グローバル高速化設定
# -------------------------------------------------------------

[build]
# コンパイルキャッシュの有効化
rustc-wrapper = "sccache"

# -------------------------------------------------------------
# OS別リンカ設定
# -------------------------------------------------------------

# Linux (x86_64): mold リンカ
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

# Linux (ARM64 / aarch64): mold リンカ
[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

# Windows (MSVC): lld-link リンカ
[target.x86_64-pc-windows-msvc]
linker = "lld-link"

# ※macOS (Apple Silicon / Xcode 15+):
# 標準リンカが最初から高速化されているため、ターゲット設定は不要(記述なしでOK)です

待ち時間を削ることで、思考のリズムを取り戻す

Rustのコンパイラが提供してくれる安全性や型システムの恩恵は計り知れません。しかし、思考と試行のサイクルがあまりに重いと、「とりあえず書いて動かしてみる」というアジャイルな実験が億劫になりがちです。

  • 高速リンカ で最後の数秒〜十数秒の詰まりを解消する
  • sccache でクレート再構築の無駄をローカル全体から排除する
  • cargo-nextest でテストを堅牢かつ軽快に回す

この3つの手当てを行うだけで、コードを書き換えてからフィードバックを得るまでの時間は半分以下に短縮されます。

並行して複数ブランチでの修正や動作確認を行う機会が多い方は、ディレクトリを行き来する際の再ビルドを防ぐ git worktree と組み合わせると、さらに開発のストレスを減らすことができます。

開発環境は一度整えてしまえば、以後のすべてのプロジェクトで静かに効果を発揮し続けます。Rustのビルド時間に少しでも引っかかりを感じていた方は、ぜひ手元の環境で試してみてください。

Lowom 編集長
この記事を書いた人:Lowom 編集長 現役Webエンジニア

都内IT企業に勤める業界20年のWebエンジニア。業務効率化・自動化スクリプトや快適な開発環境の構築、厳選したツール・ガジェットの活用法など、手元で実際に検証したリアルな一次情報をお届けします。

運営者プロフィール詳細を見る