Rustにおける大規模プロジェクトの管理を極める

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/9563950c-2ff9-438a-a226-afcce3a45353.png)](https://leapcell.io/?lc_t=q_rustprojorg)

## Rust プロジェクトの構造

Rust を学習している多くの方がよく困惑するのは、「自分のプロジェクトのファイル構造が正しいのか？標準的なのか？」という点です。そこで、今回は基本的な `main.rs` と `lib.rs` から始めて、大規模な Rust プロジェクトのコード構成について詳しく見ていきましょう。

## Crate（クレート）

- **Crate** は Rust の基本的なコンパイル単位です。各 Crate は独立したコンパイルターゲットとなり、ライブラリ（**lib crate**）または実行可能ファイル（**binary crate**）のどちらかになります。
- 1 つの Crate には「ルートファイル」があり、ライブラリ Crate の場合は `src/lib.rs`、バイナリ Crate の場合は `src/main.rs` になります。

## Package（パッケージ）

しかし、最も基本的な Rust プロジェクトに `main.rs` または `lib.rs` だけがあるのでは不十分です。

**Package（パッケージ）** は、1 つまたは複数の Crate を含む集合であり、`Cargo.toml` および `Cargo.lock` ファイルを持っています。これらのファイルは、パッケージのメタデータや依存関係を定義するために使用されます。

実際のプロジェクトでは、Crate はコードとモジュールのみを含み、パッケージの管理やビルドは `Cargo.toml` と `Cargo.lock` によって行われます。

例えば、`cargo new sdk` コマンドを使ってライブラリを作成すると、以下のようなファイル構造になります。

### ファイル構造の例

```
// ライブラリプロジェクトの場合
sdk/
├── Cargo.toml
├── Cargo.lock
└── src
    └── lib.rs

または

// 実行可能ファイルプロジェクトの場合
sdk/
├── Cargo.toml
├── Cargo.lock
└── src
    └── main.rs
```

### TOML ファイルについて

TOML ファイルは、依存関係やバージョン情報を管理するためのものです。例えば、次のように記述します。

```toml
[package]
name = "sdk"
version = "0.1.0"
edition = "2021"

# 詳細な設定については、公式ドキュメントを参照してください。
# https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
```

例えば、エラーハンドリングを簡単にする `thiserror` クレートを追加する場合、次のコマンドを実行できます。

```sh
cargo add thiserror
```

すると、`Cargo.toml` は以下のように更新されます。

```toml
[package]
name = "sdk"
version = "0.1.0"
edition = "2021"

[dependencies]
thiserror = "1.0.61"
```

## テストコードとベンチマーク

ここまでで、すでにある程度まとまったプロジェクトを作成しました。しかし、プロジェクトに欠かせないものとして、**単体テスト** や **ベンチマーク**（性能テスト）があります。では、これらのテスト用ファイルはどこに配置するのが良いのでしょうか？

Rust の公式標準やコミュニティの慣習としては、`src` ディレクトリと同じ階層に `tests/` と `benches/` のディレクトリを作成するのが一般的です。例えば、`sdk` プロジェクトの場合、以下のようになります。

```
sdk/
  ├── Cargo.toml
  ├── src/
  │   └── lib.rs
  ├── tests/
  │   ├── some-integration-tests.rs
  │   └── multi-file-test/
  │       ├── main.rs
  │       └── test_module.rs
  └── benches/
      ├── large-input.rs
      └── multi-file-bench/
          ├── main.rs
          └── bench_module.rs
```

最初は、単体テスト（unit test）を各ファイルの末尾に直接記述することもできます。これにより、`multi-file-test` のようなフォルダを作成する必要はありません。しかし、開発が進むにつれて、テストコードの量が増えていきます。そのため、コードの可読性を保つためにも、テストコードを `tests/` ディレクトリに分離することが推奨されます。

- `tests/` フォルダ：機能テストを保存するためのディレクトリで、機能が正しく動作するかを検証します。
- `benches/` フォルダ：性能テスト（ベンチマーク）を保存するためのディレクトリで、特定の処理のパフォーマンスを測定します（例：API のレスポンスタイムの測定）。

## Workspace（ワークスペース）

では、もし**1 つの大規模プロジェクトが複数の Rust プロジェクトで構成される場合**はどうすればよいでしょうか？

例えば、`sdk` は 1 人の開発者が担当する Rust プロジェクトだとします。しかし、これを基盤として `cli`（コマンドラインツール）や `server`（サーバーアプリケーション）を構築する必要があるとします。この場合、3 つのプロジェクトをそれぞれ別々に管理するのは混乱を招く可能性があります。

このような場合に役立つのが **Workspace（ワークスペース）** です。

Rust の**ワークスペース**は、**複数のパッケージを統一的に管理する仕組み**であり、以下のようなメリットがあります。

### ワークスペースの利点

- **複数のパッケージを整理**：ワークスペースを使うことで、ライブラリ（lib crate）、CLI ツール（CLI crate）、サーバーアプリ（server crate）などを一元管理できます。
- **依存関係の共有**：ワークスペースに含まれるすべてのパッケージは、共通の `Cargo.lock` を使用し、**同じバージョンの依存ライブラリを利用**できます。これにより、依存関係のバージョン不整合を防げます。
- **ビルドプロセスの簡素化**：ワークスペースのルートディレクトリで `cargo build` や `cargo test` を実行すると、すべてのサブパッケージがまとめてビルド・テストされます。
- **一貫性の向上**：共通の `Cargo.lock` を使用することで、バージョンの統一ができ、全体の一貫性が保たれます。

### ワークスペースの構造

例えば、`sdk`、`cli`、`server` の 3 つのプロジェクトを **1 つのワークスペース** にまとめると、以下のようなファイル構造になります。

```
my_workspace/
├── Cargo.lock
├── Cargo.toml
├── crates/
│   ├── sdk/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   └── lib.rs
│   │   ├── tests/
│   │   │   ├── some-integration-tests.rs
│   │   │   └── multi-file-test/
│   │   │       ├── main.rs
│   │   │       └── test_module.rs
│   ├── cli/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   └── main.rs
│   │   ├── bin/
│   │   │   ├── named-executable.rs
│   │   │   ├── another-executable.rs
│   │   │   └── multi-file-executable/
│   │   │       ├── main.rs
│   │   │       └── some_module.rs
│   │   ├── tests/
│   │   │   ├── some-integration-tests.rs
│   │   │   └── multi-file-test/
│   │   │       ├── main.rs
│   │   │       └── test_module.rs
│   └── server/
│       ├── Cargo.toml
│       ├── src/
│       │   └── main.rs
│       ├── bin/
│       │   ├── named-executable.rs
│       │   ├── another-executable.rs
│       │   └── multi-file-executable/
│       │       ├── main.rs
│       │       └── some_module.rs
│       ├── tests/
│       │   ├── some-integration-tests.rs
│       │   └── multi-file-test/
│       │       ├── main.rs
│       │       └── test_module.rs
│       ├── benches/
│       │   ├── large-input.rs
│       │   └── multi-file-bench/
│       │       ├── main.rs
│       │       └── bench_module.rs
```

### ワークスペースの設定（Cargo.toml）

#### ワークスペースを宣言

まず、ワークスペースを使用するために、**ルートの `Cargo.toml`** に以下の記述を追加します。

```toml
[workspace]
resolver = "2"
```

この `resolver = "2"` の設定は、Cargo の**依存関係解決アルゴリズムのバージョン 2**を使用することを示しています。これは、特にワークスペース内のパッケージ間の依存関係をより適切に管理するために推奨されています。

#### ワークスペースのパッケージ情報

ルートの `Cargo.toml` にワークスペースの基本情報を定義します。

```toml
[workspace.package]
name = "my-workspace"
version = "0.1.0"
edition = "2021"
```

#### ワークスペースのメンバーを指定

次に、ワークスペースに含めるサブパッケージ（メンバー）を定義します。

```toml
[workspace]
members = [
    "crates/sdk",
    "crates/cli",
    "crates/server",
]
```

これにより、ワークスペース内の全てのサブプロジェクトが一括でビルド・管理されるようになります。

#### ワークスペースでの依存関係の管理

ワークスペース全体で共通の依存関係を定義することができます。

```toml
[workspace.dependencies]
thiserror = "1.0.61"
```

これにより、`sdk`、`cli`、`server` のすべてのプロジェクトで **同じバージョンの `thiserror` クレートを使用** できます。

例えば、`sdk`、`cli`、`server` のそれぞれの `Cargo.toml` で、以下のように `thiserror` を `workspace` から参照することができます。

```toml
[dependencies]
thiserror.workspace = true
```

これにより、個々の `Cargo.toml` に依存関係を個別に記述する必要がなくなり、**バージョン管理の一貫性** が確保されます。

### ワークスペース内のパッケージ間の相互参照

ワークスペース内の異なるパッケージ間で相互に参照できるようにするには、`workspace.dependencies` にそれぞれのパッケージを追加します。

```toml
[workspace.dependencies]
thiserror = "1.0.61"

# ワークスペース内の他のメンバー
cli = { path = "crates/cli" }
server = { path = "crates/server" }
sdk = { path = "crates/sdk" }
```

そして、例えば `cli` と `server` の `Cargo.toml` で `sdk` を利用する場合は、以下のように定義します。

```toml
[dependencies]
thiserror.workspace = true
sdk.workspace = true
```

これにより、`cli` と `server` から `sdk` の機能を簡単に利用できるようになります。

## まとめ

- **Rust のプロジェクト構成** には `crate`、`package`、`workspace` という概念がある。
- **`Cargo.toml` を使ってパッケージを管理** し、ワークスペースを利用すれば **複数の Rust プロジェクトを統一的に管理** できる。
- **ワークスペースを活用することで、依存関係のバージョン管理が簡単になり、コンパイルの効率が向上** する。

Rust で大規模なプロジェクトを構築する際は、ぜひワークスペースの概念を活用してください！

---

### [私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_rustprojorg)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/907dc8ea-6093-4ece-8588-62637d4345a4.png)](https://leapcell.io/?lc_t=q_rustprojorg)

[Leapcell](https://leapcell.io/?lc_t=q_rustprojorg)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/093c1d5d-93dc-41da-84fb-b8e40ebc695e.png)](https://leapcell.io/?lc_t=q_rustprojorg)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/how-to-organize-a-large-scale-rust-project-effectively)