創業期にRustを選んだ理由とは 「エンジニアに愛される言語」への挑戦

投稿日時:
中村 秀樹のアイコン

Gen-AX株式会社 / テックリード

中村 秀樹

はじめまして。Gen-AX(ジェナックス)株式会社 テックリードの中村です。当社が開発するSaaSプロダクトのバックエンドにRustを採用した理由についてご紹介します。Gen-AXは、2023年7月に創業したばかりの会社ですが、この新しい環境でRustを採用した背景や、そのメリットについてご紹介します。

私は2024年7月、Gen-AXの初期立ち上げメンバーとしてジョインし、現在はテックリードとして技術選定などを行っています。組み込みエンジニアとしてキャリアをスタートし、その後Web系に移りました。

Gen-AXは、ソフトバンク株式会社の100%子会社として設立されました。主な事業として、B2BのSaaSプロダクトの開発・提供、生成AIの活用に伴う業務改善を支援するコンサルティングがあります。

生成AIを活用してコンタクトセンターにおける照会応答業務の効率化を支援する「X-Boost(クロスブースト)」を展開しており、コンタクトセンターに特化した検索拡張生成(RAG)を実装しています。生成AIの活用ではハルシネーションが起きやすいですが、RAGでは関連情報を検索し、その情報を基に回答を生成することで誤回答を減らすことができます。X-Boostは、お客さま自身で検索用データの登録やエンベディングモデルのファインチューニングまでを行えるのが特徴です。

X-Boostのシステム構成と技術スタック

下図は、X-Boostのシステム構成です。左側では、オペレーターや管理者などのユーザーが一つのブラウザからアクセスして検索や回答の作成を行います。モデルの管理やデータの登録もブラウザ上で完結できる仕様です。

フロントエンドの背後には、APIとして提供されるバックエンドがあり、ユーザーの認証はIdP(アイデンティティプロバイダー)で行います。認証・認可の結果、正規のリクエストだけがRustベースのバックエンドに流れるイメージです。バックエンドでは、エンベディングを提供するサーバーやベクターデータベースとの連携、データを格納するためのバッチ処理などが動作しています。

X-Boostで採用している主な技術スタックは、フロントエンドがTypeScriptとNext.js、IdPがオープンソースのKeycloak、AI部分がPython、バックエンドの言語がRust、フレームワークがaxumです。一般的には、既存のレガシーシステムをRustへ置き換えるケースも多いですが、われわれは新規のバックエンド開発段階からRustを採用しています。

X-Boost_技術スタック.png

決め手は"エンジニアに愛される言語"

バックエンドの開発言語としてRustを選んだ背景をご説明します。B2BのSaaSを開発している当社は、大企業が機微な情報を扱うケースも多いため、高い信頼性が求められます。その観点から、以下の技術選定方針に基づいて言語やフレームワークを検討しました。

まず、チームでの開発に適していることから、静的型付け言語を前提としました。関数型言語をそのまま採用するわけではありませんが、型の表現力を生かした設計や、Railway Oriented Programming(ROP)によるエラーハンドリングの明確化など、質の高いコードを作る上で参考になる関数型言語の思想を取り入れることを考えました。

ROPとは、成功時の結果とエラーの両方を扱う「Result型」を関数の戻り値にして、処理のパイプラインを構築する設計手法です。Webアプリケーションでは、リクエストを受け付け、バリデート、処理、結果を返す、という流れが多いですが、途中で失敗するとエラーレスポンスを返すことになります。ROPを採用することで、エラーハンドリングの実装を簡素化でき、正常系処理の流れが明確になります。

ROPとは.png

バックエンド言語として、チーム開発がしやすい静的型付け言語という条件で選定したところ、Kotlin、Go、Java、Rustの4つが候補になりました。そこから"型の表現力があり、ROPを使ったエラーハンドリングを実装しやすいか"という基準で絞った結果、KotlinとRustの2つが残りました。

最終的には、プロダクトとの親和性という点でRustを選びました。データの前処理が多いX-BoostではRustが適しているほか、大手のお客さまはSaaSではなくプライベートクラウド環境へのデプロイを要望する可能性があることから、リバースエンジニアリングのしにくさも考慮しました。

"ゼロから開発するタイミングだったからこそできた選択"というのも大きかったです。Kotlinも魅力的ですが、Rustは“エンジニアに愛される言語”と評されています。もし合わなければ別の言語に切り替えればよいと割り切りつつ、チャレンジングな選択としてRustを導入しました。

Rustで実現した型安全性とエラーハンドリング

型の表現力を生かす取り組みでは、Newtypeパターンによる型安全性の向上に向けて、中身は同じ型でも意味論的に異なる型を定義し、引数の不一致をコンパイラで検知しています。例えば、ModelIdTestsetIdはULIDで管理していますが、それぞれ別の型と定義することで"誤ってModelIdTestsetIdに渡してしまう"といったバグを防ぐようにしています。Newtypeの活用は、品質を高める上で有効な手段だといえます。

Rustのエラーハンドリングにおける鍵として、Result型と?演算子を活用したROPの実装があります。Rustは、Goと比較してROPの実装が特にシンプルです。Goの場合、エラーが返ってきたらif err != nil { return err }というように分岐を追加するコードが多くなりがちですが、Rustでは?演算子を使うと1行で表現できます。

ROPとは_2.png

ただし、ROPを採用すれば全てが解決するわけではありません。Rustでは"回復可能なエラーはResultで返し、回復不可能なエラーはpanic!を呼び出す"という方針がありますが、回復可能/不可能の判断には慎重になる必要があります。

エラー種別を細かくしすぎると"エラー定義地獄"に陥ってコードの見通しが悪くなる一方、まとめすぎると必要な情報が欠落してデバッグ時に苦労するリスクがあります。こうした粒度のバランスを取るのは、Rustに限らずエラー設計の難しさだといえるでしょう。

Rust導入のメリットと難しさ

Rustを導入したメリットとして、コンパイルが通った場合の品質への安心感が大きいことがあります。他言語ではテストコードで担保することが多いですが、Rustではコンパイルが通ることがある意味テストだといえます。コンパイルエラーがゼロになるまで多少苦労するものの、コンパイルが通れば"このコードはある程度堅牢に動く"と安心できる点が大きいです。

コードの見通しも良くなりました。if文によるエラー分岐を?演算子で簡潔に書けるので、正常系のロジックがすっきりと見通せます。Rustは独自型(Newtypeなど)を定義する敷居が低く、関数宣言に意図や仕様を残しやすいのも特徴です。

一方、Rustならではの難しさも存在します。例えば、サンプルコードや情報が少ないため、オープンソースのコードを確認したり、ChatGPTやGitHub Copilotを活用したりしていますが、十分な補完が得られないこともあり、この部分は改善中という印象です。

crate(ライブラリ)のバージョンアップで大きな変更が入ることも多く、追従に苦労することがあります。加えて、ビルドやリンクでメモリ使用量が膨らみやすく、ビルド時間も長くなる傾向にあるので、こうしたリソースをどう抑えるかが課題です。今後は、依存crateの見直し、データ処理実装の最適化、継続的インテグレーション(CI)にかかる時間の短縮などに取り組む予定です。

当社では現在、エンジニアを広く募集しています。Rustは未経験でも問題ありません。生成AIを活用した優れたプロダクトを作り、価値を提供したい方はぜひご検討ください。[1]

Loading...Loading...Loading...
脚注
  1. 本記事は、2025年4月17日に開催されたイベントの内容を元に編集した記事です

プロフィール