ローカル環境でのみ動作する、ローカルLLMを活用したデスクトップアプリを開発することになった。対応プラットフォームは macOS と Windows。フレームワーク選定にあたって Claude Code に相談したところ Tauri を推薦してきた。
自分は Rust を書いたことがなかったが、結論から言うと、AI エージェントにコードを書いてもらう前提であれば Rust 未経験でもまったく問題なかった。
なぜ Tauri なのか
AI に相談した結果
推薦された主な理由は以下のとおり。
- バイナリサイズが小さい — Electron と比較して圧倒的に軽量。ローカルLLMだけでもリソースを消費するため、アプリ自体は軽いほうがよい
- OS ネイティブの WebView を利用 — Chromium をバンドルしないため、メモリ消費が少ない
- Rust バックエンド — ローカルLLMのプロセス管理やファイルシステム操作など、システム寄りの処理を安全に書ける
- セキュリティモデル — ローカルで動くとはいえ、権限を細かく制御できる設計はプラス
他のクロスプラットフォーム開発ツールとの比較
Electron、Flutter Desktop、React Native など他のフレームワークとの詳しい比較はクロスプラットフォーム開発ツール比較にまとめている。ローカルLLMを動かす環境ではメモリが貴重なリソースになるため、Tauri の軽量さが決め手になった。
技術スタック
実際に採用した技術スタックは以下のとおり。
| レイヤー | 技術 |
|---|---|
| フロントエンド | React 19 / TypeScript / Vite |
| デスクトップシェル | Tauri 2 |
| バックエンド | Rust(オーケストレーション、ストレージ、ポリシー制御) |
| ローカルLLM | llama.cpp + Qwen2.5-7B-Instruct(GGUF Q4_K_M) |
| データベース | SQLite(rusqlite) |
フロントエンドの React + Vite + TypeScript という構成も AI に選定してもらった。
アーキテクチャ
全体構成
graph TB
A[フロントエンド - WebView<br/>React / Vite / TypeScript] -- "invoke(IPC)" --> B[Tauri コマンド<br/>Rust]
B -- "プロセス起動" --> C[llama.cpp<br/>ローカルLLM]
B -- "読み書き" --> D[(SQLite)]
B -- "emit イベント" --> A
Tauri のコマンド(#[tauri::command])を通じて、フロントエンドから Rust 側の関数を呼び出す。Rust 側では外部プロセスをオーケストレーションし、進捗をイベントとしてフロントエンドにリアルタイムで返す。
Tauri コマンドの設計
Rust 側には 17 のコマンドが登録されている。責務ごとに分類すると以下のようになる。
| カテゴリ | コマンド例 |
|---|---|
| セットアップ | run_first_time_setup, install_models, run_self_test |
| ジョブ管理 | create_job, run_job, cancel_job, load_job, export_job |
| 設定・用語集 | get_settings, update_settings, upsert_glossary_term |
| 診断 | get_app_summary, get_runtime_contract, get_setup_status |
フロントエンドからは @tauri-apps/api の invoke() で呼び出し、長時間処理の進捗は listen() でイベントを受け取る。ポーリングではなくイベント駆動なので、UI はリアルタイムに更新される。
サイドカー設計
外部バイナリ(llama.cpp など)の解決には 3 段階のフォールバックを採用している。
- 環境変数オーバーライド —
LLAMA_CPP_BINなど - バンドルされたバイナリ —
binaries/macos/やbinaries/windows/配下 - システムの PATH — フォールバック
これにより、開発時はシステムにインストールされたバイナリを使い、配布時にはバンドルされたバイナリが使われる。Tauri の bundle.resources 設定でリソースをアプリに同梱できる。
{
"bundle": {
"resources": [
"../resources",
"../binaries"
]
}
}ローカルLLMの活用
llama.cpp を使い、Qwen2.5-7B-Instruct(GGUF Q4_K_M 形式、約 4.7 GB)をローカルで実行している。
特徴的なのは JSON スキーマ制約付きの生成 を採用している点。llama.cpp の --json-schema オプションで出力構造を強制し、Rust 側でバリデーションを行う。不正な出力にはリトライロジックが適用される。
また、長いテキストは 6,000 文字ごとにチャンク分割し、各チャンクを独立して要約した後、最終的にマージパスで統合する。トークン制限を回避しつつ、構造化された出力を生成する設計になっている。
Rust が書けなくても問題ない理由
正直なところ、Tauri を選ぶうえで最大の懸念は Rust だった。しかし、実際に開発を始めてみると、この懸念はほぼ解消された。
AI エージェントがコードを書く時代
現在の開発では、Codex にコードの大部分を書いてもらっている。具体的には以下のような流れになる。
- 要件を自然言語で伝える — 「ローカルの llama.cpp にリクエストを送って、JSON スキーマ制約付きで生成したい」など
- Codex が Rust コードを生成 — Tauri のコマンド定義、エラーハンドリング(
thiserror)、型定義まで含めて出力される - ビルドして動作確認 — コンパイルエラーがあれば Codex に修正を依頼
- 細かい調整は Claude Code で — コードの意図を確認したり、リファクタリングを相談したり
Rust のコンパイラは厳格なので、AI が生成したコードでもコンパイルが通れば一定の品質が担保される。これは動的型付け言語にはない安心感がある。
自分に求められるスキル
Rust を書ける必要はないが、以下のスキルは必要だと感じている。
- AI への的確な指示出し — 何を作りたいかを具体的に伝える力
- 生成されたコードのレビュー — 完全に読めなくても、構造や意図を把握できる程度の理解
- Tauri のアーキテクチャ理解 — フロントエンド(WebView)とバックエンド(Rust)の境界を理解しておくこと
- デバッグの切り分け — 問題がフロントエンド側なのかバックエンド側なのかを判断する力
つまり「コードを書く力」よりも「設計と判断の力」が重要になっている。
まとめ
ローカルLLMを活用するクロスプラットフォームアプリの開発に Tauri を採用した。AI エージェントに相談して選定し、AI エージェントにコードを書いてもらう。Rust を自分で書けなくても、設計とレビューができれば開発は進められる。
実際のアプリでは、Rust バックエンドが llama.cpp などの外部プロセスをオーケストレーションし、SQLite でジョブを管理し、イベント駆動でフロントエンドと連携している。こうしたシステム寄りの処理こそ Rust の安全性が活きる領域であり、Tauri を選んだ判断は正しかった。
AI がコードを書く時代において、フレームワーク選定の基準は「自分が書けるかどうか」から「最適な技術かどうか」にシフトしている。Tauri は軽量でセキュアなデスクトップアプリを作るのに適した選択肢であり、ローカルLLMとの相性もよい。言語の壁は AI が埋めてくれる。
以上、Rust が書けなくても Tauri でアプリを作れる時代になった、現場からお送りしました。