Protocol Buffersは、Rust共有コンポーネントのFFI境界を「スキーマ」で守る選択だった

Mozilla Hacks記事紹介シリーズのアイキャッチ画像

執筆者:

カテゴリ:

Mozilla Hacksの「Crossing the Rust FFI frontier with Protocol Buffers」は、Firefox SyncなどのMozilla Application Servicesを、複数プロダクトで再利用できるRust共有コンポーネントへ寄せていく過程を紹介した記事です。

当時の課題は、Firefox Desktop、Android、iOSで同じような機能を別々の実装として持っていたことでした。SyncであればJavaScript、Java、Swiftの実装が並び、機能差やバグ修正の反映漏れが起きやすくなります。そこで中核ロジックをRustで共通化し、各プラットフォームにはKotlinやSwiftなどの薄いラッパーを置く方針が採られました。

記事の要点

  • 狙いは、Firefox機能の中核ロジックを1つのRustコードベースへ集約すること。 修正や改善を各プロダクトへ個別移植するのではなく、共通コンポーネントのバージョン更新として届けられるようにします。
  • 難所はFFI境界のデータ受け渡しだった。 Rustの所有権・メモリ安全性を守りながら、KotlinやSwift側にリッチな構造化データを渡す必要があります。
  • 初期案ではJSON文字列やC風structを使っていた。 JSONは扱いやすい一方でシリアライズ/デシリアライズの負荷があり、型変更を各言語側で手作業追従する必要があります。C風structのポインタ受け渡しは、定義のズレがメモリ破壊につながり得ます。
  • Protocol Buffers v2により、.protoスキーマを単一の情報源にした。 Rust側と利用側のデータクラスを同じスキーマから生成できるため、境界の形を手作業で合わせる危険が減ります。
  • Rust側ではprostを使っていた。 Rustのderive macroを活かし、生成されるstructがRustらしく扱える点が選択理由として説明されています。

リンク先も読むと見える流れ

Application Servicesのリポジトリを見ると、この方針がFirefox Accounts、Sync、Push、ExperimentationなどをまたぐRustコンポーネント群として続いていることがわかります。現在のREADMEでは、共有Rustコードを各プラットフォーム向けのネイティブバインディングで包む構成が説明され、コンポーネントのFFI生成にはUniFFIも使われています。

記事中で紹介されるffi-supportは、RustでFFIを扱うときの補助crateです。そもそもFFIは、異なる言語やランタイム間で関数やデータをやり取りする境界であり、そこでは型・メモリ所有・文字列表現の違いがそのままバグの入口になります。

データ形式については、データシリアライズ形式の比較にあるように、多くの選択肢があります。この記事では、速度だけでなく、スキーマから複数言語の型を生成できることを重視してProtocol Buffersを選んでいます。prostはRust向けProtocol Buffers実装として、そのRust側の生成コードを担います。

また、Firefox AccountsやWebPushのような機能は、単体アプリの内部実装ではなく、複数デバイス・複数製品へ広がるサービスの接点です。だからこそ、各プラットフォームで似たものを作るのではなく、共通ロジックと薄いネイティブ層に分ける設計の意味が大きくなります。

気づき

この記事で面白いのは、Rust化の価値を「速くなる」「安全になる」だけで語っていないところです。本当の焦点は、複数プラットフォームへ同じ機能を届けるための保守境界をどこに置くかです。Rustを中核に置くことでロジックは共有できますが、FFI境界の設計が弱いと、結局Kotlin側やSwift側で型のズレを手作業で吸収することになります。

その意味でProtocol Buffersは、単なる高速なバイナリ形式というより、チーム間・言語間の契約をファイルとして固定する道具として効いています。後年のUniFFIの流れを見ると、この2019年の記事は「Rustで共有する」だけでなく、「共有したRustをどう安全に各言語へ見せるか」というMozillaの試行錯誤の途中段階として読めます。

参照した記事

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です