Rust のコマンドを JS から呼び出す (invoke)

@tauri-apps/api/core の invoke で、#[tauri::command] を付けた Rust 関数を呼ぶ基本。generate_handler! への登録と、lib.rs と main.rs の関係も示す。

フロントエンド 対象: Tauri 2.x 更新日: 読了目安: 約6分 front-001
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. npm を使わず window.__TAURI__ から呼ぶ
  4. 2. バックエンドから実装する (Rust)
  5. 動作確認
  6. よくあるエラーと対処法
  7. Command xxx not found という趣旨のエラーで reject される
  8. error[E0255]: the name __cmd__greet is defined multiple times
  9. invalid args ... for command ... という趣旨のエラー
  10. OS ごとの違いと注意点
  11. 関連レシピ

フロントエンド(JavaScript / TypeScript)から Rust の関数を呼び出す仕組みが「コマンド」です。Rust 側で #[tauri::command] を付けた関数を generate_handler! に登録し、フロントエンドから @tauri-apps/api/core の invoke で名前を指定して呼びます。ファイル操作や重い計算など「ブラウザの JS ではできないこと」を Rust に任せる入口で、プラグインは使わず core API だけで完結します。

前提条件

追加プラグインは不要です。アプリ自身が定義したコマンドは既定で権限設定なしに呼べるため、src-tauri/capabilities/default.json の編集も不要です(プラグインのコマンドは別途権限が必要です)。

1. フロントエンドから実装する (TypeScript)

invoke のシグネチャは invoke<T>(cmd: string, args?: InvokeArgs, options?: InvokeOptions): Promise<T> で、常に Promise を返します。第 1 引数はコマンド名(Rust の関数名そのまま)、第 2 引数は引数オブジェクトです。

// src/main.ts
import { invoke } from '@tauri-apps/api/core';

async function callSimple() {
  await invoke('simple_command'); // 引数なし・戻り値なし
  console.log('simple_command が完了しました');
}

async function callGreet() {
  // 戻り値の型をジェネリクスで指定する(実行時チェックはされない)
  const message = await invoke<string>('greet', { name: 'Tauri' });
  console.log(message); // "Hello, Tauri!"
}

document.querySelector('#btn-simple')?.addEventListener('click', callSimple);
document.querySelector('#btn-greet')?.addEventListener('click', callGreet);
  • await を付け忘れると戻り値が Promise のままになり、画面には [object Promise] が出ます。
  • 引数のキーは Rust 側の引数名を キャメルケース にしたものです(invoke_message なら invokeMessage)。詳細は Rust コマンドに引数を渡す を参照してください。
  • Rust 側が Err を返すと reject されるので、実運用では try / catch で囲みます。

npm を使わず window.__TAURI__ から呼ぶ

バンドラーを使わない素の HTML 構成では、tauri.conf.json の app.withGlobalTauri を true にすると window.__TAURI__ に API が注入されます。TypeScript の型が効かないので、プロトタイプ向けの方法です。

{ "app": { "withGlobalTauri": true } }
<script>
  const { invoke } = window.__TAURI__.core;
  invoke('greet', { name: 'Tauri' }).then((msg) => alert(msg));
</script>

2. バックエンドから実装する (Rust)

Tauri v2 のテンプレートでは src-tauri/src/main.rs は app_lib::run() を呼ぶだけで、実体は src-tauri/src/lib.rs の run() にあります(モバイル対応のための構成)。コマンドの定義と登録は lib.rs 側 に書きます(v1 時代の main.rs に書く手順は当てはまりません)。

// src-tauri/src/lib.rs
#[tauri::command]
fn simple_command() {
    println!("JS から呼ばれました");
}

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        // 複数のコマンドは 1 つの generate_handler! にカンマ区切りで並べる
        .invoke_handler(tauri::generate_handler![simple_command, greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  • lib.rs に直接書くコマンドには pub を付けません(グルーコード生成の制約として公式ドキュメントに明記されています)。
  • コマンドが増えたら src-tauri/src/commands.rs などに分け、そちらでは pub fn にして generate_handler![commands::greet] のようにパス付きで登録します。
  • invoke_handler を 2 回チェーンすると後勝ちで上書きされます。必ず 1 回の generate_handler! にまとめます。

動作確認

npm run tauri dev で起動してボタンを押します。

  • simple_command の println! は WebView のコンソールではなく、tauri dev を実行したターミナル に出ます。
  • greet の結果は WebView の開発者ツール(Windows / Linux は右クリック > 検証、macOS は Cmd+Option+I)のコンソールに Hello, Tauri! と出ます。

よくあるエラーと対処法

Command xxx not found という趣旨のエラーで reject される

#[tauri::command] は付けたが generate_handler! に並べ忘れた、または関数名のタイプミスです。文字列で指定するためコンパイラは検出できません。lib.rs の generate_handler![...] を確認してください。

error[E0255]: the name __cmd__greet is defined multiple times

同じ名前のコマンドを 2 か所で定義するとビルドエラーになります。#[tauri::command] はコマンド名から __cmd__xxx という補助アイテムを生成するため、モジュールが違ってもコマンド名はアプリ全体で一意にします。

invalid args ... for command ... という趣旨のエラー

引数名の不一致です。Rust 側が user_name: String なら JS 側は { userName: '...' } と書きます。スネークケースのまま渡したい場合は #[tauri::command(rename_all = "snake_case")] を付けます。

OS ごとの違いと注意点

invoke 自体の挙動に OS 差はなく、差が出るのは呼び出し先の Rust 側です。パス区切りやファイルロックなど OS 依存の処理はコマンドの中で吸収します。

  • 同期コマンド (fn) はメインスレッドで実行されるため、重い処理を書くと UI が固まります。時間のかかる処理は async fn にするか(rust-005 参照)、別スレッドに逃がします。
  • 通常のブラウザで http://localhost:1420 を直接開くと window.__TAURI__ が存在せず invoke は失敗します。isTauri()(@tauri-apps/api/core)で環境を判定すると安全です。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する