Frontend and Rust Interaction
Frontend Calling Rust Functions
Defining Rust Commands
// src-tauri/src/main.rs
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Frontend Invocation Example
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function sayHello() {
const response = await invoke('greet', { name: 'World' })
console.log(response) // "Hello, World!"
}
</script>
<template>
<button @click="sayHello">Greet</button>
</template>Rust Calling Frontend Methods
Defining Frontend Callbacks
// src-tauri/src/main.rs
#[tauri::command]
fn trigger_frontend_callback(window: tauri::Window) {
window.emit('rust-event', "Data from Rust").unwrap();
}Frontend Event Listening
// src/App.vue
<script setup>
import { listen } from '@tauri-apps/api/event'
onMounted(() => {
listen('rust-event', (event) => {
console.log(event.payload) // "Data from Rust"
})
})
</script>Data Serialization
Using Serde for Complex Data Transfer
// src-tauri/src/main.rs
use serde::{Serialize, Deserialize}
#[derive(Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
}
#[tauri::command]
fn get_user() -> User {
User {
id: 1,
name: "Alice".into(),
email: "alice@example.com".into(),
}
}Frontend Receiving Complex Data
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
interface User {
id: number
name: string
email: string
}
async function fetchUser() {
const user: User = await invoke('get_user')
console.log(user)
}
</script>Error Handling
Rust-Side Error Definition
// src-tauri/src/main.rs
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("File not found")]
FileNotFound,
#[error("Permission denied")]
PermissionDenied,
}
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
std::fs::read_to_string(path)
.map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => AppError::FileNotFound.to_string(),
std::io::ErrorKind::PermissionDenied => AppError::PermissionDenied.to_string(),
_ => e.to_string(),
})
}Frontend Error Handling
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function readFile() {
try {
const content = await invoke('read_file', { path: '/etc/secret' })
console.log(content)
} catch (error) {
if (error.includes('File not found')) {
alert('File not found!')
} else if (error.includes('Permission denied')) {
alert('Permission denied!')
} else {
alert('Unknown error: ' + error)
}
}
}
</script>Asynchronous Communication
Rust-Side Asynchronous Commands
// src-tauri/src/main.rs
use tokio::fs;
#[tauri::command]
async fn async_read_file(path: String) -> Result<String, String> {
fs::read_to_string(path)
.await
.map_err(|e| e.to_string())
}Frontend Calling Asynchronous Commands
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function loadFile() {
const content = await invoke('async_read_file', {
path: '/tmp/data.txt'
})
console.log(content)
}
</script>System API Integration
File System Operations
File Read/Write Example
// src-tauri/src/main.rs
use std::fs;
#[tauri::command]
fn write_file(path: String, content: String) -> Result<(), String> {
fs::write(path, content).map_err(|e| e.to_string())
}
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(path).map_err(|e| e.to_string())
}Frontend File Operations
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function saveNote() {
await invoke('write_file', {
path: '/tmp/note.txt',
content: 'Hello Tauri!'
})
}
async function loadNote() {
const content = await invoke('read_file', { path: '/tmp/note.txt' })
console.log(content)
}
</script>System Dialogs
File Dialog Example
// src-tauri/src/main.rs
use tauri::api::dialog;
#[tauri::command]
fn open_file_dialog() -> Result<String, String> {
dialog::FileDialogBuilder::new()
.add_filter("Text Files", &["txt"])
.pick_file()
.map(|path| path.to_string_lossy().into_owned())
.map_err(|e| e.to_string())
}Frontend Calling Dialogs
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function openFileDialog() {
const path = await invoke('open_file_dialog')
console.log('Selected file:', path)
}
</script>Clipboard Operations
Clipboard Read/Write
// src-tauri/src/main.rs
use tauri::api::clipboard;
#[tauri::command]
fn get_clipboard() -> Result<String, String> {
clipboard::read_text().map_err(|e| e.to_string())
}
#[tauri::command]
fn set_clipboard(text: String) -> Result<(), String> {
clipboard::write_text(text).map_err(|e| e.to_string())
}Frontend Clipboard Operations
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function copyToClipboard() {
await invoke('set_clipboard', { text: 'Hello from Tauri!' })
}
async function pasteFromClipboard() {
const text = await invoke('get_clipboard')
console.log('Clipboard content:', text)
}
</script>System Notifications
Sending Notifications
// src-tauri/src/main.rs
use tauri::api::notification;
#[tauri::command]
fn send_notification(title: String, body: String) -> Result<(), String> {
notification::Notification::new()
.title(&title)
.body(&body)
.show()
.map_err(|e| e.to_string())
}Frontend Calling Notifications
// src/App.vue
<script setup>
import { invoke } from '@tauri-apps/api/tauri'
async function showNotification() {
await invoke('send_notification', {
title: 'Tauri Notification',
body: 'This is a system notification'
})
}
</script>Native Menus and System Tray
Creating Menus
// src-tauri/src/main.rs
use tauri::Menu;
use tauri::MenuEntry;
fn main() {
let context = tauri::generate_context!();
tauri::Builder::default()
.menu(
Menu::new()
.add_item(MenuEntry::new("File").with_submenu(vec![
MenuEntry::new("Open").with_action(|| {
println!("Open file");
}),
MenuEntry::new("Exit").with_action(|| {
std::process::exit(0);
}),
]))
)
.run(context)
.expect("error while running tauri application");
}Creating System Tray
// src-tauri/src/main.rs
use tauri::Tray;
use tauri::TrayEvent;
fn main() {
let context = tauri::generate_context!();
tauri::Builder::default()
.system_tray(
Tray::new().with_menu(
Menu::new().add_item(MenuEntry::new("Show").with_action(|| {
println!("Show window");
})).add_item(MenuEntry::new("Exit").with_action(|| {
std::process::exit(0);
}))
)
)
.on_system_tray_event(|app, event| match event {
TrayEvent::DoubleClick { .. } => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
}
_ => {}
})
.run(context)
.expect("error while running tauri application");
}Configuration and Packaging
Tauri Configuration File
tauri.conf.json Example
{
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"distDir": "../dist"
},
"package": {
"productName": "MyTauriApp",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
"fs": {
"scope": ["$APPDATA/*", "$HOME/*"]
},
"dialog": {
"all": true
}
},
"security": {
"csp": null
},
"bundle": {
"identifier": "com.mycompany.myapp",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/256x256.png"]
}
}
}Application Icon Configuration
Preparing Icons
- Prepare PNG icons in multiple sizes (16×16, 32×32, 64×64, 128×128, 256×256).
- Create an
iconsdirectory and place the icon files.
Configuring Icon Paths
// tauri.conf.json
{
"bundle": {
"icon": [
"icons/16x16.png",
"icons/32x32.png",
"icons/64x64.png",
"icons/128x128.png",
"icons/256x256.png"
]
}
}Packaging and Distribution
Running in Development Mode
npm run tauri devBuilding for Production
npm run tauri buildPackage Output Directories
- Windows:
src-tauri/target/release/bundle/msi - macOS:
src-tauri/target/release/bundle/dmg - Linux:
src-tauri/target/release/bundle/appimage
Automatic Update Mechanism
Configuring Automatic Updates
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.setup(|app| {
let window = app.get_window("main").unwrap();
// Check for updates
tauri::async_runtime::spawn(async move {
match tauri::updater::check_update(&app.config()).await {
Ok(update) if update.is_update_available() => {
window.emit("update-available", update).unwrap();
}
_ => {}
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Frontend Handling Updates
// src/App.vue
<script setup>
import { listen } from '@tauri-apps/api/event'
onMounted(() => {
listen('update-available', (event) => {
if (confirm('New version found. Update now?')) {
// Trigger update download and installation
}
})
})
</script>Multi-Platform Packaging and Testing
Cross-Platform Packaging Commands
# Windows
npm run tauri build -- --target x86_64-pc-windows-msvc
# macOS
npm run tauri build -- --target x86_64-apple-darwin
# Linux
npm run tauri build -- --target x86_64-unknown-linux-gnuTesting Strategies
Unit Testing: Rust code unit tests
cargo testIntegration Testing: Frontend-Rust interaction tests
npm run testEnd-to-End Testing: Full application workflow tests
npm run tauri testCross-Platform Validation:
- Conduct UI adaptation tests on target platforms.
- Verify file system permissions.
- Test system API compatibility.



