フロントエンド(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)で環境を判定すると安全です。
