Swayに学ぶ実用Rustパターン Part 1: モジュラーモノレポと拡張可能なCLI設計
1. 概要
Rustは、パフォーマンスと信頼性を両立するシステムプログラミング言語として、WebAssembly、オペレーティングシステム、さらにはブロックチェーンといった多岐にわたる分野で採用されています。
本記事シリーズでは、Fuelブロックチェーン向けのスマートコントラクト言語Swayのツールチェイン (forc) を分析し、大規模なRustプロジェクトで役立つ実践的な設計パターンと実装テクニックを学びます。
Part 1となる今回は、特に「モジュラーモノレポアーキテクチャ」と「拡張可能なコマンドラインインターフェース (CLI) の設計」に焦点を当てます。SwayプロジェクトがどのようにRustのワークスペースを活用し、forcツールがどのようにしてそのエコシステムの中心となり、さらにプラグインによって拡張されているのかを見ていきましょう。
2. アーキテクチャ
Swayプロジェクトは、Rustのワークスペース機能を用いたモジュラーモノレポとして構築されています。これにより、関連する複数のクレート(ライブラリやバイナリ)を単一のリポジトリで管理し、依存関係の調整や一貫したバージョニングを容易にしています。
プロジェクトの中核をなすのは、Sway言語のコンパイラ群 (sway-core, sway-ast, sway-irなど) と、その開発ツールチェインであるforcです。
forcはRustのCargoに相当する役割を果たし、Swayプロジェクトのビルド、テスト、デプロイなど、開発ワークフロー全体をオーケストレーションします。その機能は、forc-pkg (パッケージ管理)、forc-test (テスト実行) といったコアクレートに加え、forc-plugins/*ディレクトリに存在する多様なプラグイン (forc-doc, forc-fmt, forc-lspなど) によって拡張されています。
forcツールチェインの構造
forcは、ユーザーからの入力を受け付け、内部のパッケージ管理クレートや外部プラグインと連携することで、Sway開発者に統合された体験を提供します。
この図からわかるように、forcは単一の実行可能ファイルとして機能しながら、その実態は複数のクレートとプラグインによって構成される、高度にモジュール化されたシステムです。これにより、各機能が独立して開発・保守されつつ、統一されたユーザーインターフェースを提供しています。
3. この記事で学べること
本記事を通じて、読者の皆さんは以下の実践的な知識とパターンを習得できます。
- Rustワークスペースを活用した大規模プロジェクト管理のノウハウ
forcツールチェインに学ぶ拡張可能なCLIツールの設計思想- Facadeパターンを用いたAPIの単純化テクニック
- 設定ファイルをデータとして扱う堅牢なアプローチ
- プロジェクトの安定性を高める依存関係の厳格なバージョン管理
4. 実践的な実装・コード解説
4.1. Rustワークスペースによるモジュラーモノレポ
Swayプロジェクトは、リポジトリルートのCargo.tomlでワークスペースを定義しています。これは、複数のクレートを一つのリポジトリで管理する標準的な方法です。
# リポジトリルートのCargo.toml (抜粋)
[workspace]
resolver = "2"
members = [
"forc",
"forc-pkg",
"forc-test",
"forc-util",
"forc-plugins/*", # ワイルドカードでプラグインクレートを検出
"sway-ast",
"sway-core",
# ... その他多数のクレート
]
[workspace.package]
authors = ["Fuel Labs <fuel@fuel.sh>"]
license = "Apache-2.0"
repository = "https://github.com/FuelLabs/sway"
# ... 共通のパッケージメタデータ
[workspace.dependencies]
tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] }
# ... 共通の依存関係
membersフィールドはワークスペースに含まれるクレートのパスを指定します。forc-plugins/*のようにワイルドカードを使用することで、新しいプラグインの追加が容易になります。また、[workspace.package]や[workspace.dependencies]セクションでは、ワークスペース内の全クレートに適用される共通の設定や依存関係を定義でき、一貫性と管理のしやすさを向上させています。
4.2. forc-pkgに学ぶFacadeパターン
forc-pkgクレートは、Swayプロジェクトのパッケージ管理を担う中心的な役割を果たします。そのlib.rsは、内部の複雑なモジュール構造を抽象化し、主要な型をトップレベルで再エクスポートするFacadeパターンを採用しています。
// forc-pkg/src/lib.rs (抜粋)
pub mod lock;
pub mod manifest;
pub mod pkg;
pub mod source;
pub use lock::Lock;
pub use manifest::{BuildProfile, Manifest, PackageManifest, PackageManifestFile};
pub use pkg::{BuiltPackage, Dependency, LocalManifest, Pin,
Pinned, PinnedDep, PinnedManifest, Pkg, Rev}; // PinnedManifest は pkg::PinnedManifest の間違いか?
pub use source::{DependencySource, Source};
// ... その他、内部モジュールから必要なものを再エクスポート
このようにpub useを多用することで、forc_pkg::Lockやforc_pkg::PackageManifestのように、クレートの利用者は内部モジュールの詳細を知らずとも、必要な型にアクセスできます。これは、ライブラリのAPIをシンプルかつ使いやすくするための強力な手法です。
4.3. 内部スクリプトによる依存関係の厳格な管理
Swayプロジェクトには、check-dep-versionsというRust製のスクリプトが存在します。これは、Cargo.tomlで定義されている依存関係のバージョンが厳格なルール (x.y.z形式またはx.y形式) に従っているか、また許可された依存関係のリストに含まれているかを自動でチェックします。
// scripts/check-dep-versions/src/main.rs (抜粋)
use std::collections::HashSet;
// ... 省略
// 許可された依存関係の名前のHashSet
let allowed_xyz_set: HashSet<String> = ALLOWED_XYZ_DEPS.iter().map(|s| s.to_string()).collect();
// ... 省略
// 依存関係が許可リストに含まれているか、バージョン形式が正しいかをチェック
fn check_pkg(
dep: &Dependency,
allowed_xyz_set: &HashSet<String>,
allowed_xy_set: &HashSet<String>,
) -> Result<(), Error> {
// ... バージョン形式と許可リストのチェックロジック
}
// ... 省略
HashSet<String>を使用して、許可された依存関係名を効率的にルックアップしている点に注目です。これは、ビルドの安定性を高め、予期せぬ依存関係の更新による問題を未然に防ぐための、実用的な品質保証の取り組みです。CI/CDパイプラインに組み込むことで、プロジェクト全体の整合性を維持できます。
5. 実務に持ち帰れるTips
Tip 1: Rustワークスペースを最大限に活用したモノレポ設計
- 内容: Swayプロジェクトのように、関連性の高い複数のクレート(ライブラリ、バイナリ、テスト)はRustワークスペースで一元管理することで、開発の効率化と一貫性を図れます。共通の依存関係やビルド設定はワークスペースレベルで定義し、個々のクレートの
Cargo.tomlをシンプルに保ちましょう。 - なぜ役立つか: 大規模プロジェクトにおける依存関係地獄を回避し、内部クレート間のバージョン衝突を防ぎます。特にコンパイラやツールチェインのように、密接に連携するコンポーネントが多い場合に威力を発揮します。
Tip 2: 拡張可能なCLIアーキテクチャの構築
- 内容:
forcのように、CLIツールはコア機能とプラグインシステムを明確に分離して設計します。clapのようなライブラリを使ってサブコマンドを定義し、外部プラグインとして機能を追加できるようなメカニズムを提供します。 - なぜ役立つか: コアツールの肥大化を防ぎつつ、コミュニティや特定のユースケースに合わせた機能拡張を可能にします。将来の機能追加やカスタマイズが容易になり、長期的な保守性も向上します。
Tip 3: FacadeパターンによるライブラリAPIの単純化
- 内容:
forc-pkgが実践しているように、ライブラリの内部構造が複雑な場合でも、pub useステートメントを活用して主要な型や関数をクレートのトップレベルで再エクスポートし、外部利用者向けのAPIを単純化します。 - なぜ役立つか: ユーザーはライブラリの深いモジュール構造を意識することなく、必要な機能に直感的にアクセスできます。APIの学習コストが下がり、使い勝手が向上します。
Tip 4: 設定ファイルをデータとして扱う堅牢なパターン
- 内容:
Forc.tomlやCargo.tomlのように、TOMLなどの構造化されたファイル形式で設定を定義し、それをRustの型にパースしてプログラム内で厳密に扱うパターンを採用します。これにより、設定の検証やアクセスが型安全になります。 - なぜ役立つか: 実行時エラーのリスクを減らし、設定の誤りをコンパイル時または早期に検出できます。コードによる設定値の操作も容易になります。
Tip 5: CI/CDと連携した依存関係の厳格なバージョン管理
- 内容:
check-dep-versionsスクリプトのように、CI/CDパイプラインに依存ライブラリのバージョン制約をチェックする仕組みを導入します。許可されたバージョン形式や、特定の依存関係のホワイトリストを定義し、それを自動で検証します。 - なぜ役立つか: 予期せぬ依存関係の変更によるビルドの失敗やセキュリティ脆弱性の混入を防ぎ、プロジェクトの安定性と信頼性を維持します。特に、サプライチェーン攻撃のリスクが高いシステムにおいては非常に重要です。
6. トレードオフと注意点
6.1. モノレポの複雑性とビルド時間
モノレポは、依存関係管理を単純化し、開発の一貫性を保つ一方で、プロジェクトが大規模になるにつれてビルド時間が長くなる傾向があります。Swayプロジェクトでは、justコマンド (just build-all) を用いて効率的なビルドを試みていますが、適切に設定しないと開発体験を損なう可能性があります。
6.2. プラグインシステムの管理コスト
拡張可能なCLIは非常に強力ですが、コアツールとプラグイン間のバージョン互換性維持、APIの安定性保証、ユーザーへの適切なドキュメント提供など、管理コストが発生します。プラグイン開発者とユーザー双方にとって使いやすいエコシステムを構築するための継続的な努力が必要です。
6.3. 厳格な依存関係バージョニングのオーバーヘッド
check-dep-versionsスクリプトによる厳格なバージョン管理は、ビルドの安定性向上に寄与しますが、新しい依存関係の追加や既存の依存関係のバージョンアップには、その都度許可リストの更新が必要となります。これは開発者にとって追加のオーバーヘッドとなる可能性があり、安定性と柔軟性の間のバランスを考慮する必要があります。
7. まとめ
Swayプロジェクトの分析を通じて、私たちはRustを用いた大規模システム開発におけるいくつかの重要なパターンを学びました。
- Rustワークスペースは、複数のクレートを統合し、モジュラーモノレポを構築するための強力な基盤となります。
forcツールチェインは、拡張可能なCLI設計の優れた例であり、コア機能とプラグインの分離により高い柔軟性を提供します。- Facadeパターンや設定ファイルをデータとして扱うアプローチは、ライブラリの使いやすさとシステムの堅牢性を高めます。
- 依存関係の厳格なバージョン管理は、プロジェクトの安定性と品質を維持するための重要なプラクティスです。
これらのパターンは、Swayのようなブロックチェーン関連のツールチェインに限らず、あらゆる大規模なRustプロジェクトに応用可能です。ぜひ皆さんのプロジェクトにこれらの知見を持ち帰り、より堅牢で保守しやすいシステム開発に役立ててください。
次回Part 2では、Swayコンパイラの内部に焦点を当て、コンパイラ設計におけるRustの活用方法、特にAST (抽象構文木) の管理や、Visitorパターンの利用などについて深く掘り下げていきます。