calling-rust-from-tauri-frontend
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCalling Rust from Tauri Frontend
从Tauri前端调用Rust代码
This skill covers how to call Rust backend functions from your Tauri v2 frontend using the command system and invoke function.
本技能介绍如何使用命令系统和invoke函数从Tauri v2前端调用Rust后端函数。
Overview
概述
Tauri provides two IPC mechanisms:
- Commands (recommended): Type-safe function calls with serialized arguments/return values
- Events: Dynamic, one-way communication (not covered here)
Tauri提供两种IPC机制:
- 命令(推荐):类型安全的函数调用,支持参数/返回值序列化
- 事件:动态的单向通信(本文不涉及)
Basic Commands
基础命令
Defining a Command in Rust
在Rust中定义命令
Use the attribute macro:
#[tauri::command]rust
// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}使用属性宏:
#[tauri::command]rust
// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}Registering Commands
注册命令
Commands must be registered with the invoke handler:
rust
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, login, fetch_data])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}命令必须注册到invoke处理器中:
rust
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, login, fetch_data])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}Invoking from JavaScript/TypeScript
在JavaScript/TypeScript中调用
typescript
import { invoke } from '@tauri-apps/api/core';
const greeting = await invoke('greet', { name: 'World' });
console.log(greeting); // "Hello, World!"Or with the global Tauri object (when is enabled):
app.withGlobalTaurijavascript
const { invoke } = window.__TAURI__.core;
const greeting = await invoke('greet', { name: 'World' });typescript
import { invoke } from '@tauri-apps/api/core';
const greeting = await invoke('greet', { name: 'World' });
console.log(greeting); // "Hello, World!"或者使用全局Tauri对象(当启用时):
app.withGlobalTaurijavascript
const { invoke } = window.__TAURI__.core;
const greeting = await invoke('greet', { name: 'World' });Passing Arguments
传递参数
Argument Naming Convention
参数命名规范
By default, Rust snake_case arguments map to JavaScript camelCase:
rust
#[tauri::command]
fn create_user(user_name: String, user_age: u32) -> String {
format!("{} is {} years old", user_name, user_age)
}typescript
await invoke('create_user', { userName: 'Alice', userAge: 30 });Use to change the naming convention:
rename_allrust
#[tauri::command(rename_all = "snake_case")]
fn create_user(user_name: String, user_age: u32) -> String {
format!("{} is {} years old", user_name, user_age)
}默认情况下,Rust的snake_case参数会映射到JavaScript的camelCase:
rust
#[tauri::command]
fn create_user(user_name: String, user_age: u32) -> String {
format!("{} is {} years old", user_name, user_age)
}typescript
await invoke('create_user', { userName: 'Alice', userAge: 30 });使用修改命名规范:
rename_allrust
#[tauri::command(rename_all = "snake_case")]
fn create_user(user_name: String, user_age: u32) -> String {
format!("{} is {} years old", user_name, user_age)
}Complex Arguments
复杂参数
Arguments must implement :
serde::Deserializerust
use serde::Deserialize;
#[derive(Deserialize)]
struct UserData {
name: String,
email: String,
age: u32,
}
#[tauri::command]
fn register_user(user: UserData) -> String {
format!("Registered {} ({}) age {}", user.name, user.email, user.age)
}typescript
await invoke('register_user', {
user: { name: 'Alice', email: 'alice@example.com', age: 30 }
});参数必须实现:
serde::Deserializerust
use serde::Deserialize;
#[derive(Deserialize)]
struct UserData {
name: String,
email: String,
age: u32,
}
#[tauri::command]
fn register_user(user: UserData) -> String {
format!("Registered {} ({}) age {}", user.name, user.email, user.age)
}typescript
await invoke('register_user', {
user: { name: 'Alice', email: 'alice@example.com', age: 30 }
});Returning Values
返回值
Simple Return Types
简单返回类型
Return types must implement :
serde::Serializerust
#[tauri::command]
fn get_count() -> i32 { 42 }
#[tauri::command]
fn get_message() -> String { "Hello from Rust!".into() }typescript
const count: number = await invoke('get_count');
const message: string = await invoke('get_message');返回类型必须实现:
serde::Serializerust
#[tauri::command]
fn get_count() -> i32 { 42 }
#[tauri::command]
fn get_message() -> String { "Hello from Rust!".into() }typescript
const count: number = await invoke('get_count');
const message: string = await invoke('get_message');Returning Complex Types
返回复杂类型
rust
use serde::Serialize;
#[derive(Serialize)]
struct AppConfig {
theme: String,
language: String,
notifications_enabled: bool,
}
#[tauri::command]
fn get_config() -> AppConfig {
AppConfig {
theme: "dark".into(),
language: "en".into(),
notifications_enabled: true,
}
}typescript
interface AppConfig {
theme: string;
language: string;
notificationsEnabled: boolean;
}
const config: AppConfig = await invoke('get_config');rust
use serde::Serialize;
#[derive(Serialize)]
struct AppConfig {
theme: String,
language: String,
notifications_enabled: bool,
}
#[tauri::command]
fn get_config() -> AppConfig {
AppConfig {
theme: "dark".into(),
language: "en".into(),
notifications_enabled: true,
}
}typescript
interface AppConfig {
theme: string;
language: string;
notificationsEnabled: boolean;
}
const config: AppConfig = await invoke('get_config');Returning Binary Data
返回二进制数据
For large binary data, use to bypass JSON serialization:
tauri::ipc::Responserust
use tauri::ipc::Response;
#[tauri::command]
fn read_file(path: String) -> Response {
let data = std::fs::read(&path).unwrap();
Response::new(data)
}typescript
const data: ArrayBuffer = await invoke('read_file', { path: '/path/to/file' });对于大型二进制数据,使用绕过JSON序列化:
tauri::ipc::Responserust
use tauri::ipc::Response;
#[tauri::command]
fn read_file(path: String) -> Response {
let data = std::fs::read(&path).unwrap();
Response::new(data)
}typescript
const data: ArrayBuffer = await invoke('read_file', { path: '/path/to/file' });Error Handling
错误处理
Using Result Types
使用Result类型
Return where implements or is a :
Result<T, E>ESerializeStringrust
#[tauri::command]
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Cannot divide by zero".into())
} else {
Ok(a / b)
}
}typescript
try {
const result = await invoke('divide', { a: 10, b: 0 });
} catch (error) {
console.error('Error:', error); // "Cannot divide by zero"
}返回,其中需要实现或为类型:
Result<T, E>ESerializeStringrust
#[tauri::command]
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Cannot divide by zero".into())
} else {
Ok(a / b)
}
}typescript
try {
const result = await invoke('divide', { a: 10, b: 0 });
} catch (error) {
console.error('Error:', error); // "Cannot divide by zero"
}Custom Error Types with thiserror
使用thiserror定义自定义错误类型
rust
use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("File not found: {0}")]
FileNotFound(String),
#[error("Permission denied")]
PermissionDenied,
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
impl Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
serializer.serialize_str(self.to_string().as_ref())
}
}
#[tauri::command]
fn open_file(path: String) -> Result<String, AppError> {
if !std::path::Path::new(&path).exists() {
return Err(AppError::FileNotFound(path));
}
let content = std::fs::read_to_string(&path)?;
Ok(content)
}rust
use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("File not found: {0}")]
FileNotFound(String),
#[error("Permission denied")]
PermissionDenied,
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
impl Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
serializer.serialize_str(self.to_string().as_ref())
}
}
#[tauri::command]
fn open_file(path: String) -> Result<String, AppError> {
if !std::path::Path::new(&path).exists() {
return Err(AppError::FileNotFound(path));
}
let content = std::fs::read_to_string(&path)?;
Ok(content)
}Structured Error Responses
结构化错误响应
rust
use serde::Serialize;
#[derive(Debug, Serialize)]
struct ErrorResponse { code: String, message: String }
#[tauri::command]
fn validate_input(input: String) -> Result<String, ErrorResponse> {
if input.is_empty() {
return Err(ErrorResponse {
code: "EMPTY_INPUT".into(),
message: "Input cannot be empty".into(),
});
}
Ok(input.to_uppercase())
}typescript
interface ErrorResponse { code: string; message: string; }
try {
const result = await invoke('validate_input', { input: '' });
} catch (error) {
const err = error as ErrorResponse;
console.error(`Error ${err.code}: ${err.message}`);
}rust
use serde::Serialize;
#[derive(Debug, Serialize)]
struct ErrorResponse { code: String, message: String }
#[tauri::command]
fn validate_input(input: String) -> Result<String, ErrorResponse> {
if input.is_empty() {
return Err(ErrorResponse {
code: "EMPTY_INPUT".into(),
message: "Input cannot be empty".into(),
});
}
Ok(input.to_uppercase())
}typescript
interface ErrorResponse { code: string; message: string; }
try {
const result = await invoke('validate_input', { input: '' });
} catch (error) {
const err = error as ErrorResponse;
console.error(`Error ${err.code}: ${err.message}`);
}Async Commands
异步命令
Defining Async Commands
定义异步命令
Use the keyword:
asyncrust
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url).await.map_err(|e| e.to_string())?;
let body = response.text().await.map_err(|e| e.to_string())?;
Ok(body)
}使用关键字:
asyncrust
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url).await.map_err(|e| e.to_string())?;
let body = response.text().await.map_err(|e| e.to_string())?;
Ok(body)
}Async with Borrowed Types Limitation
异步命令的借用类型限制
Async commands cannot use borrowed types like directly:
&strrust
// Will NOT compile:
// async fn bad_command(value: &str) -> String { ... }
// Use owned types instead:
#[tauri::command]
async fn good_command(value: String) -> String {
some_async_operation(&value).await;
value
}
// Or wrap in Result as workaround:
#[tauri::command]
async fn with_borrowed(value: &str) -> Result<String, ()> {
some_async_operation(value).await;
Ok(value.to_string())
}异步命令不能直接使用等借用类型:
&strrust
// 无法编译:
// async fn bad_command(value: &str) -> String { ... }
// 改用拥有类型:
#[tauri::command]
async fn good_command(value: String) -> String {
some_async_operation(&value).await;
value
}
// 或者用Result作为变通方案:
#[tauri::command]
async fn with_borrowed(value: &str) -> Result<String, ()> {
some_async_operation(value).await;
Ok(value.to_string())
}Frontend Invocation
前端调用
Async commands work identically to sync since returns a Promise:
invoketypescript
const result = await invoke('fetch_data', { url: 'https://api.example.com/data' });异步命令的调用方式与同步命令相同,因为返回Promise:
invoketypescript
const result = await invoke('fetch_data', { url: 'https://api.example.com/data' });Accessing Tauri Internals
访问Tauri内部资源
WebviewWindow, AppHandle, and State
WebviewWindow、AppHandle和State
rust
use std::sync::Mutex;
struct AppState { counter: Mutex<i32> }
#[tauri::command]
async fn get_window_label(window: tauri::WebviewWindow) -> String {
window.label().to_string()
}
#[tauri::command]
async fn get_app_version(app: tauri::AppHandle) -> String {
app.package_info().version.to_string()
}
#[tauri::command]
fn increment_counter(state: tauri::State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
pub fn run() {
tauri::Builder::default()
.manage(AppState { counter: Mutex::new(0) })
.invoke_handler(tauri::generate_handler![
get_window_label, get_app_version, increment_counter
])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}rust
use std::sync::Mutex;
struct AppState { counter: Mutex<i32> }
#[tauri::command]
async fn get_window_label(window: tauri::WebviewWindow) -> String {
window.label().to_string()
}
#[tauri::command]
async fn get_app_version(app: tauri::AppHandle) -> String {
app.package_info().version.to_string()
}
#[tauri::command]
fn increment_counter(state: tauri::State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
pub fn run() {
tauri::Builder::default()
.manage(AppState { counter: Mutex::new(0) })
.invoke_handler(tauri::generate_handler![
get_window_label, get_app_version, increment_counter
])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}Advanced Features
高级特性
Raw Request Access
访问原始请求
Access headers and raw body:
rust
use tauri::ipc::{Request, InvokeBody};
#[tauri::command]
fn upload(request: Request) -> Result<String, String> {
let InvokeBody::Raw(data) = request.body() else {
return Err("Expected raw body".into());
};
let auth = request.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok())
.ok_or("Missing Authorization header")?;
Ok(format!("Received {} bytes", data.len()))
}typescript
const data = new Uint8Array([1, 2, 3, 4, 5]);
await invoke('upload', data, { headers: { Authorization: 'Bearer token123' } });访问请求头和原始请求体:
rust
use tauri::ipc::{Request, InvokeBody};
#[tauri::command]
fn upload(request: Request) -> Result<String, String> {
let InvokeBody::Raw(data) = request.body() else {
return Err("Expected raw body".into());
};
let auth = request.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok())
.ok_or("Missing Authorization header")?;
Ok(format!("Received {} bytes", data.len()))
}typescript
const data = new Uint8Array([1, 2, 3, 4, 5]);
await invoke('upload', data, { headers: { Authorization: 'Bearer token123' } });Channels for Streaming
使用Channel实现流式传输
rust
use tauri::ipc::Channel;
use tokio::io::AsyncReadExt;
#[tauri::command]
async fn stream_file(path: String, channel: Channel<Vec<u8>>) -> Result<(), String> {
let mut file = tokio::fs::File::open(&path).await.map_err(|e| e.to_string())?;
let mut buffer = vec![0u8; 4096];
loop {
let len = file.read(&mut buffer).await.map_err(|e| e.to_string())?;
if len == 0 { break; }
channel.send(buffer[..len].to_vec()).map_err(|e| e.to_string())?;
}
Ok(())
}typescript
import { Channel } from '@tauri-apps/api/core';
const channel = new Channel<Uint8Array>();
channel.onmessage = (chunk) => console.log('Received:', chunk.length, 'bytes');
await invoke('stream_file', { path: '/path/to/file', channel });rust
use tauri::ipc::Channel;
use tokio::io::AsyncReadExt;
#[tauri::command]
async fn stream_file(path: String, channel: Channel<Vec<u8>>) -> Result<(), String> {
let mut file = tokio::fs::File::open(&path).await.map_err(|e| e.to_string())?;
let mut buffer = vec![0u8; 4096];
loop {
let len = file.read(&mut buffer).await.map_err(|e| e.to_string())?;
if len == 0 { break; }
channel.send(buffer[..len].to_vec()).map_err(|e| e.to_string())?;
}
Ok(())
}typescript
import { Channel } from '@tauri-apps/api/core';
const channel = new Channel<Uint8Array>();
channel.onmessage = (chunk) => console.log('Received:', chunk.length, 'bytes');
await invoke('stream_file', { path: '/path/to/file', channel });Organizing Commands in Modules
模块化组织命令
rust
// src-tauri/src/commands/user.rs
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct CreateUserRequest { pub name: String, pub email: String }
#[derive(Serialize)]
pub struct User { pub id: u32, pub name: String, pub email: String }
#[tauri::command]
pub fn create_user(request: CreateUserRequest) -> User {
User { id: 1, name: request.name, email: request.email }
}rust
// src-tauri/src/commands/mod.rs
pub mod user;rust
// src-tauri/src/lib.rs
mod commands;
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![commands::user::create_user])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}rust
// src-tauri/src/commands/user.rs
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct CreateUserRequest { pub name: String, pub email: String }
#[derive(Serialize)]
pub struct User { pub id: u32, pub name: String, pub email: String }
#[tauri::command]
pub fn create_user(request: CreateUserRequest) -> User {
User { id: 1, name: request.name, email: request.email }
}rust
// src-tauri/src/commands/mod.rs
pub mod user;rust
// src-tauri/src/lib.rs
mod commands;
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![commands::user::create_user])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}TypeScript Type Safety
TypeScript类型安全
Create a typed wrapper:
typescript
import { invoke } from '@tauri-apps/api/core';
export interface User { id: number; name: string; email: string; }
export interface CreateUserRequest { name: string; email: string; }
export const commands = {
createUser: (request: CreateUserRequest): Promise<User> =>
invoke('create_user', { request }),
greet: (name: string): Promise<string> =>
invoke('greet', { name }),
};
// Usage
const user = await commands.createUser({ name: 'Bob', email: 'bob@example.com' });创建类型化的封装函数:
typescript
import { invoke } from '@tauri-apps/api/core';
export interface User { id: number; name: string; email: string; }
export interface CreateUserRequest { name: string; email: string; }
export const commands = {
createUser: (request: CreateUserRequest): Promise<User> =>
invoke('create_user', { request }),
greet: (name: string): Promise<string> =>
invoke('greet', { name }),
};
// 使用示例
const user = await commands.createUser({ name: 'Bob', email: 'bob@example.com' });Quick Reference
快速参考
| Task | Rust | JavaScript |
|---|---|---|
| Define command | | - |
| Register command | | - |
| Invoke command | - | |
| Return value | | |
| Return error | | |
| Async command | | Same as sync |
| Access window | | - |
| Access app | | - |
| Access state | | - |
| 任务 | Rust代码 | JavaScript代码 |
|---|---|---|
| 定义命令 | | - |
| 注册命令 | | - |
| 调用命令 | - | |
| 返回值 | | |
| 返回错误 | | |
| 异步命令 | | 与同步命令相同 |
| 访问窗口 | | - |
| 访问应用实例 | | - |
| 访问状态 | | - |
Key Constraints
关键约束
- Command names must be unique across the entire application
- Commands in cannot be
lib.rs(use modules for organization)pub - All commands must be registered in a single call
generate_handler! - Async commands cannot use borrowed types like directly
&str - Arguments must implement , return types must implement
DeserializeSerialize
- 命令名称在整个应用中必须唯一
- lib.rs中的命令不能设为pub(使用模块组织命令)
- 所有命令必须在同一个调用中注册
generate_handler! - 异步命令不能直接使用等借用类型
&str - 参数必须实现,返回类型必须实现
DeserializeSerialize