「C++の既存コードベースにRustを導入したい」。こんな悩みを抱えている開発者は、意外と多いのではないでしょうか。今回は、ソフトウェアテスト企業のAntithesis社が公開した、シングルスレッドC++とマルチスレッドRustを連携させた事例を紹介してみたいと思います。

C++ Rust 連携が必要になった背景

Antithesis社は、決定論的ハイパーバイザー(Determinator)を使ったソフトウェアテストを提供している企業です。彼らのファザー(バグ探索ツール)はシングルスレッドのC++で書かれていましたが、新しい制御戦略をより効率的に実装するため、Rustの導入を決めたそうです。

ここで厄介なのが、C++側はシングルスレッド・同期処理で動いているのに対し、Rust側はマルチスレッド・非同期処理という、まったく異なるパラダイムで設計されている点です。この2つをどう繋げるかが、技術的な挑戦だったわけですね。

cxxクレートによるFFI(Foreign Function Interface)

C++とRustの橋渡しには、cxxクレートが使われています。cxxは、C++とRust間のFFI(Foreign Function Interface)を安全に構築するためのツールで、以下の3種類のインターフェースを定義できます。

  • extern Rust types: C++から呼び出せるRust型。cxxがC++ヘッダーを自動生成してくれます。
  • extern C++ types: Rustから呼び出せるC++型。Rust側で関数シグネチャを定義すると、cxxが呼び出し規約を変換してくれるんですよね。
  • shared structures: メソッドを持たない純粋な構造体。C++とRust間で自由に受け渡しできます。

同期C++と非同期Rustの接続パターン

最も興味深いのは、同期的なC++のメインループと非同期的なRustのコントローラーをどう接続したかという部分です。C++側は以下のようなコールバック型のインターフェースを持っています。

C++のメインループは、(1) コントローラーのpoll_for_inputsメソッドを呼んで「どこから始めて何をすべきか」を聞き、(2) advertise_outputsメソッドを呼んで「結果はこうだった」と伝えるという流れになっています。

一方、Rust側のコントローラーは「ここから始めて、この入力を与えて、結果をawaitする」という非同期インターフェースを採用しているわけです。

実装上のポイント

Antithesis社が採用したアプローチには、いくつかの重要なポイントがあります。

1. Tokioランタイムの活用

Rust側ではTokioの非同期ランタイムを使い、マルチスレッドで制御戦略を実行しています。C++からRustへの呼び出し時に、Tokioのランタイムに処理を委譲する形をとっているようです。

2. チャネルベースの通信

同期と非同期の境界を越えるため、チャネル(channel)を使ったメッセージパッシングが採用されています。C++側のコールバックがRust側のチャネルにデータを送り、非同期タスクがそれを受け取るという仕組みですね。

3. 安全性の確保

cxxクレートの大きなメリットは、unsafe コードの範囲を最小限に抑えられることです。FFIの呼び出し規約の変換はcxxが自動で行ってくれるため、開発者が手動でunsafeブロックを書く必要がほとんどありません。

この事例から学べること

Antithesis社の事例は、「既存のC++コードベースを全面的に書き換えずにRustを導入する」という現実的なアプローチを示してくれています。MiniMax M2.5とは?SWE-bench 80%超えの中国発AIモデルが示すオープンソースAIの実力の記事でも紹介したように、新しい技術の導入には段階的なアプローチが有効なケースが多いものです。

とくに参考になるのは、以下の点ではないでしょうか。

  • パフォーマンスクリティカルな既存コード(C++)はそのまま維持しつつ、新機能をRustで実装できる
  • cxxクレートにより、型安全性を保ちながらFFIを構築できる
  • 同期・非同期の境界はチャネルで解決できるパターンがある

MiniMax M2.5とは?SWE-bench 80%超えの中国発AIモデルが示すオープンソースAIの実力でも触れていますが、言語間の連携技術は今後ますます重要になってくるかもしれません。

まとめ

C++とRustの連携は、理論的には可能でも実践的なノウハウが不足しがちな領域です。Antithesis社の事例は、シングルスレッドC++とマルチスレッドRustという難しい組み合わせを、cxxクレートとTokioランタイムで解決した貴重な実践例と言えます。既存のC++プロジェクトにRustを段階的に導入したいと考えている方には、大いに参考になるのではないでしょうか。

参考: How we interfaced single-threaded C++ with multi-threaded Rust (Antithesis)