Gemcook Tech Blog
⚙️

【Tauri】フロントからRustを実行する invoke API に触れる

に公開

はじめに

Tauri で開発を行っていても、通常のWeb開発の範囲での機能にとどめる場合は、Rust のコードに触れる機会はほとんどありません。しかし、OS の機能を直接利用するような場面では、Rust 側での実装が必要になります。本記事では、フロントエンドから直接 Rust の処理を呼び出す方法について解説します。

概要

Tauri には、フロントエンドから Rust コードを呼び出すための Invoke API が用意されています。本記事では、この API の実装手順を解説します。

詳細は公式ドキュメントをご覧ください。
https://v2.tauri.app/ja/develop/calling-rust/
https://v2.tauri.app/ja/reference/javascript/api/namespacecore/#invoke

本記事では理解を深めるために、以下のステップで段階的に実装を進めます。

  1. ボタンのクリックイベントから Rust 関数を呼び出す。
  2. 単一の引数を渡す・簡単なレスポンスを返す。
  3. 複数の引数を渡す。
  4. エラーハンドリング(おまけ)

実装

今回は幾つかのAPIを追加するだけですので、 src-tauri/src/main.rs だけの変更です。

ボタンのクリックイベントから Rust 関数を呼び出す

src-tauri/src/main.rs
use tauri::command;

// ① フロントから呼び出したい関数を作成する
#[tauri::command]
fn custum_command() {
    println!("Hello, World!");
}

fn main() {
    tauri::Builder::default()
        // ② invoke_handlerに作成した関数を追加する
        .invoke_handler(tauri::generate_handler![custum_command])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

#[tauri::command] は、Tauri が提供する「属性マクロ(attribute macro)」で、Rust 側の関数をフロントエンド(JavaScript/TypeScript)から呼び出せるように登録するためのものです。

登録されると、ビルド時に Tauri のコマンド一覧に自動的に追加され、フロントエンドの invoke("message") 経由で呼び出せるようになります!

実際にやってみたのが以下のファイルです。

index.tsx
import React from 'react';
import { invoke } from '@tauri-apps/api/tauri';

const App: React.FC = () => {
  const callMessage = () => {
    invoke('custum_command');
  };

  return (
      <button onClick={() => callMessage()}>
        Rust の custum_command を呼び出す
      </button>
  );
};

export default App;

ここではわかりやすく invoke('custom_command') としていますが invoke API は Promise を返すため、必要に応じて await.then() で非同期処理を挟んでください。
普段通りの非同期関数の使い方と同じなので、馴染みやすく使いやすい API だと思います。

単一の引数を渡す・簡単なレスポンスを返す。

次は引数を渡して、Rust側で文字を合成して返してくれる関数を新しく作成してみます。

src-tauri/src/main.rs
#[tauri::command]
// ① Rustは明示的に返り値も必要なので -> String で型をつける。
fn say_hello(name: String) -> String {
    // ② (セミコロンがないので)この式が return される
    format!("Hello, {}!", name)
}

fn main() {
    tauri::Builder::default()
        // ③ say_helloを追加する
        .invoke_handler(tauri::generate_handler![custom_command, say_hello])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
index.tsx
// 呼び出し例
const callMessage = () => {
    invoke('say_hello', { name: 'Tauri' }).then((res) => {
        console.log(res) // "Hello, Tauri!"
    });
}

複数の引数を渡す

src-tauri/src/main.rs
#[tauri::command]
// 引数の Vec<T> は TypeScript の Array<T> に相当する可変長配列
// 返り値の i32 は 32 ビット符号付き整数(-2,147,483,648 ~ 2,147,483,647)
fn sum(numbers: Vec<String>) -> i32 {
// Vec内の数値をイテレータで順に取り出し、sum() で全要素を合計して返します。
numbers.into_iter().sum()
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![custom_command, say_hello, sum])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
index.tsx
// 呼び出し例
const callAdd = async () => {
  try {
    const sum: number = await invoke('add', { numbers: [4, 5, 6] });
  } catch (error) {
    console.error(error);
  }
};

エラーハンドリング(おまけ)

この例では、Rust 関数 might_failResult 型を返します。Tauri では、Rust 側で Err を返すと、JavaScript 側では catch 節で例外として処理されます。

src-tauri/src/main.rs
#[tauri::command]
// Rust では関数の戻り値に「Result<T, E>」型を使うことで、
// 成功 (Ok) と 失敗 (Err) の両方を表現することができます。
fn might_fail(flag: bool) -> Result<String, String> {
    if flag {
        Ok("Success!".into()) // 成功ケース
    } else {
        Err("Something went wrong".into()) // 失敗ケース
    }
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![might_fail])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
index.tsx
const callMightFail = async () => {
  try {
    // false を渡すと Err が返る
    const result: string = await invoke('might_fail', { flag: false });
    alert(result);
  } catch (error) {
    // Rust 側の Err("Something went wrong") がここでキャッチされる
    console.error('Error:', error);
    alert(`Error: ${error}`);
  }
};

まとめ

普段からフロントエンドに触れていれば、簡単に API を追加・作成できるのではないでしょうか。

他にも Tauri には、フロントエンドから送信されたイベントを Rust 側で受け取る仕組みや、逆に Rust 側からフロントエンドに向けてイベントを送信する機能があります。さらに、ストリーム通信を用いた継続的なデータのやり取りなど多彩な機能があります。

私自身 Tauri の機能や Rust自体への理解はまだまだですが、少しずつ勉強していきたいと思います。
フロントエンドとRustの橋渡しとして、invoke API は非常に便利です。ぜひ活用してみてください。

Gemcook Tech Blog
Gemcook Tech Blog

Discussion