脱・肥大化:TauriとRustで軽量なデスクトップアプリを構築する

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

午前2時のメモリリークという悪夢

監視アラートが鳴り響いたのは、ちょうど午前2時14分のことでした。運用チームのために作成したサーバー監視用の小さなユーティリティアプリが、合計8GBしか積んでいないマシンのメモリを2.5GBも食いつぶしていたのです。犯人は?

たった3つのボタンとテキストログを表示するためだけに、Chromiumのフルインスタンスを実行するElectronেরラッパーでした。その瞬間、私は方向転換が必要だと悟りました。UIにはウェブ技術の機敏さが、バックエンドにはシステム言語の生(なま)の効率性が必要だったのです。

日が昇るまでに、私はそのユーティリティをTauriに移植しました。メモリ使用量は2.5GBから、なんと45MBまで激減しました。単純なツールのために180MBもの実行ファイルを配布することに苦痛を感じたことがあるなら、これがなぜ重要か理解できるはずです。Tauriは、肥大化よりもパフォーマンスを優先する開発者にとっての救世主です。

アーキテクチャ:なぜTauriが選ばれるのか

ほとんどのデスクトップフレームワークは、重いブラウザエンジンをアプリケーション本体に無理やり詰め込みます。一方、TauriはOS標準のWebView(WindowsならWebView2、macOSならWebKit、LinuxならGTK)を活用するという賢明なルートを選択しています。UIはHTMLとCSSのままですが、エンジンルームはRustによって駆動されます。

この切り離し(デカップリング)には2つの大きな利点があります。第一に、バイナリサイズが極めて小さくなることです。ユーザーはすでにOSレベルでブラウザをインストールしているため、ブラウザを同梱する必要がないからです。第二に、Rustのメモリ安全性と並行性の保証を享受できることです。現場での経験から言えば、これは「ウェブ開発者」から、プロフェッショナル級のデスクトップツールを構築できる「ソフトウェアエンジニア」へとステップアップする最も効果的な方法です。

戦場のセットアップ

Rust環境のインストールは、特にWindowsではコンパイラとの格闘のように感じることがあります。単一のインストーラーをダウンロードして終わり、というわけにはいきません。重い処理を担う特定のC++ツールチェーンが必要になります。

1. 事前準備

Windowsでは、Visual Studio Installerを介して 「C++によるデスクトップ開発」 ワークロードをインストールする必要があります。これを怠ると、Rustコンパイラ(rustc)がバイナリをリンクしようとした瞬間にクラッシュします。Macユーザーはもっと簡単で、xcode-select --installを実行すれば依存関係が処理されます。UbuntuやDebianの場合は、システムWebViewと連携するために以下のライブラリが必要です:

bash
sudo apt update
sudo apt install libwebkit2gtk-4.0-dev \
    build-essential \
    curl \
    wget \
    file \
    libssl-dev \
    libgtk-3-dev \
    libayatana-appindicator3-dev \
    librsvg2-dev

2. Rustのインストール

rustupを使用しましょう。これはシステムパスを汚さずにバージョンを管理するための標準的な手法です。

bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

プロジェクトの雛形作成

手動設定で発生しがちな設定エラーを避けるため、私は通常 create-tauri-app ユーティリティを使用します。ターミナルを開いて以下を実行してください:

bash
npx create-tauri-app@latest

このチュートリアルでは、**React** と **npm** を使用します。CLIでプロンプトが表示されたら、使い慣れたUIテンプレートを選択してください。ただし、序盤から依存関係の泥沼に陥るのを避けるため、パッケージマネージャーはシンプルなものにしておきましょう。

ブリッジ:Rustコマンド

Tauriアプリの核心は **プロセス間通信(IPC)** です。Rustで関数を書き、マクロでタグ付けすれば、JavaScriptから直接呼び出すことができます。ここで多くの開発者が、シリアライズできない複雑なオブジェクトをブリッジ経由で渡そうとして躓いてしまいます。

src-tauri/src/main.rs を開いてください。ここに主要なロジックを記述します。今回は、JavaScript単体では安全に実行できないタスクである「システムファイルの読み込み」を行うコマンドを作成します。

rust
#[tauri::command]
fn read_secret_config(path: String) -> Result<String, String> {
    // 本番環境ではディレクトリトラバーサルを防ぐためにパスの検証を行う必要があります
    std::fs::read_to_string(path)
        .map_err(|e| e.to_string())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![read_secret_config])
        .run(tauri::generate_context!())
        .expect("Tauriアプリケーションの実行中にエラーが発生しました");
}

Reactコンポーネント側では、Tauri APIを使用してこのバックエンド関数を実行できます:

javascript
import { invoke } from '@tauri-apps/api/tauri';

async function handleFileRead() {
  try {
    const content = await invoke('read_secret_config', { path: '/etc/hosts' });
    console.log('ファイル内容:', content);
  } catch (error) {
    console.error('アクセスが拒否されました:', error);
  }
}

パッケージングと配布

アプリのビルドこそ、Tauriの真価が発揮される場面です。単に生の .exe を出力するだけでなく、そのまま配布可能なインストーラーを生成してくれます。Pythonベースのツールを使っていた頃はこれが悪夢でしたが、Tauriは Windows用の .msi、Mac用の .dmg、Linux用の .deb を自動的に処理します。

ビルドを実行します:

bash
npm run tauri build

初回実行時はRustが必要なクレート(ライブラリ)をダウンロードするため、3分から5分ほどかかります。コーヒーでも飲みながら待ちましょう。完了したら src-tauri/target/release/bundle/ を確認してください。署名済みのインストーラーが見つかるはずです。サイズはおそらく4.5MB以下、高解像度の写真1枚分程度でしょう。

クロスコンパイルの現実

厳しい現実をお伝えします。MacBook上でWindows用の .exe をネイティブにビルドすることはできません。回避策はありますが、不安定です。プロフェッショナルな解決策は **GitHub Actions** です。CI/CDワークフローを使用して、3つのプラットフォームすべてのビルドを同時にトリガーします。これにより、アップデートを公開するためだけに3台の物理的なワークステーションを維持する必要がなくなります。

結論

Tauriへの切り替えは、補助輪を外したような感覚でした。Rustの厳格さに従う必要はありましたが、その見返りはユーザーに好まれる超高速なアプリケーションでした。CPUを占領することも、バッテリーを消耗させることもありません。ウェブからデスクトップへの移行には、セキュリティとローカルの状態管理に深く焦点を当てる必要があります。

次にデスクトップツールの作成を依頼されたら、重くて肥大化したフレームワークを使いたい誘惑に打ち勝ってください。代わりにRustとTauriのスタックを試してみてください。ユーザーのマシンのメモリも喜び、あなたも「午前2時の緊急呼び出し」を心配することなくぐっすり眠れるようになるでしょう。

Share: