前のレシピ (rust-014) では Serde の設定方法を解説しました。
ここでは、実際に Tauri コマンドから多様なデータを返す際の実践的なパターンを紹介します。
1. 基本的なオブジェクトを返す
Serialize を実装した構造体は、Tauri コマンドの戻り値としてそのまま指定できます。
use serde::Serialize;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Product {
id: i32,
name: String,
price: f64,
in_stock: bool,
}
#[tauri::command]
fn get_product() -> Product {
Product {
id: 101,
name: "Mechanical Keyboard".into(),
price: 120.50,
in_stock: true,
}
}
JavaScript (TypeScript) 側での受け取り:
import { invoke } from '@tauri-apps/api/core';
// 型定義をしておくと開発が楽になります
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
// ジェネリクスで戻り値の型を指定
const product = await invoke<Product>('get_product');
console.log(product.name);
2. リスト(配列)を返す
Vec<T> は JavaScript の配列 Array ([]) に変換されます。
#[tauri::command]
fn get_tags() -> Vec<String> {
vec!["rust".into(), "tauri".into(), "typescript".into()]
}
JavaScript (TypeScript) 側での受け取り:
import { invoke } from '@tauri-apps/api/core';
const tags = await invoke<string[]>('get_tags');
console.log(tags); // ['rust', 'tauri', 'typescript']
3. マップ(辞書)を返す
HashMap<K, V> は JavaScript のオブジェクト {} または Map に変換されます。
(キーが文字列の場合はオブジェクトになります)
use std::collections::HashMap;
#[tauri::command]
fn get_metrics() -> HashMap<String, i32> {
let mut map = HashMap::new();
map.insert("visits".into(), 1500);
map.insert("clicks".into(), 300);
map
}
JavaScript (TypeScript) 側での受け取り:
import { invoke } from '@tauri-apps/api/core';
// HashMap<String, i32> は Record<string, number> に相当します
const metrics = await invoke<Record<string, number>>('get_metrics');
console.log(metrics.visits); // 1500
4. Enum (列挙型) の高度な変換
Rust の強力な Enum も JSON に変換できます。
フロントエンドでの扱いやすさを考えて、tag を指定するのがおすすめです。
おすすめ: Tagged Enum
#[serde(tag = "type")] をつけると、バリアント名を指定したフィールド(ここでは type)に入れたオブジェクトになります。
use serde::Serialize;
#[derive(Serialize)]
#[serde(tag = "type", rename_all = "camelCase")] // typeフィールドで判別、フィールド名はキャメルケース
enum WindowEvent {
Resize { width: u32, height: u32 },
Focus,
}
#[tauri::command]
fn get_last_event() -> WindowEvent {
WindowEvent::Resize { width: 800, height: 600 }
}
JavaScript (TypeScript) 側:
import { invoke } from '@tauri-apps/api/core';
interface WindowEvent {
type: 'resize' | 'focus';
width?: number;
height?: number;
}
// { type: "resize", width: 800, height: 600 }
// または { type: "focus" }
const event = await invoke<WindowEvent>('get_last_event');
if (event.type === 'resize') {
// width, height の存在チェックが必要な場合がありますが、
// Rust側で Resize バリアントなら必ず含まれるため、型定義次第でそのままアクセス可能です
console.log(`Resized to ${event.width}x${event.height}`);
}
まとめ
- 構造体: JS オブジェクト
{}になる。rename_all="camelCase"を推奨。 - Vec: JS 配列
[]になる。 - Option:
Some(v)はv、Noneはnullになる。 - Enum: 設定次第だが、
#[serde(tag = "...")]を使うと JS で扱いやすい。