← Back to Desktop application starters · project planning pack

Skill · Version 1.0.0

Tauri and Rust integration

Integrates Rust commands and native processes into Tauri with nonblocking execution, explicit permissions and controlled shutdown.

tauri-rust-integration

Agents using this skill

See how it helps

When is it useful?

Use it when desktop UI actions call Rust, share application state or start a bundled helper process.

What does it help you do?

  • Uses nonblocking commands with typed serializable errors.
  • Protects managed shared state and narrowly scoped capabilities.
  • Packages target-specific sidecars and manages their process lifecycle.

Try asking this

Add a local sidecar that stops cleanly when the Tauri window exits. Start the packaged sidecar, cancel an active command and close the app. Verify typed errors reach the UI and no owned native process survives.

Detailed documentation

Read the skill’s original instructions, examples and checks in their authored language.

Details…Show less

Tauri & Rust Integration

Tauri pairs a system WebView frontend with a Rust core process. Keep the Rust core responsive, type-safe, and governed by Tauri v2's fine-grained capability security model.

Designing Safe, Non-Blocking #[tauri::command] Handlers

  1. Use async commands for all I/O and subprocess work:
    • Synchronous #[tauri::command] fn my_cmd() runs on the main UI thread and freezes the window if it blocks on disk, SQLite, or child processes. Declare #[tauri::command] async fn my_cmd(...) -> Result<OutputDto, AppError>.
  2. Serializable, typed error handling (Result<T, AppError>):
    • Implement serde::Serialize on a structured AppError { code: &'static str, message: String } (using thiserror) so the frontend receives machine-readable error codes rather than opaque string panics.
  3. Thread-safe managed state (tauri::State):
    • Register shared application state (app.manage(AppState { ... })) wrapped in tokio::sync::Mutex / RwLock (for async guards held across .await) or std::sync::Mutex (only for fast synchronous lock/unlock blocks—never hold a std::sync::MutexGuard across .await!).
  4. Streaming events & binary payloads:
    • For high-frequency progress streams or terminal/agent logs, use tauri::ipc::Channel<EventPayload> passed as a command argument (or scoped app.emit_to(window_label, ...)), which avoids global broadcast overhead.

Tauri v2 Capabilities & Permissions (src-tauri/capabilities/)

Tauri v2 enforces an explicit Access Control List (ACL) per window/webview:

  • Define capability files in src-tauri/capabilities/default.json targeting specific windows ("windows": ["main"]).
  • Grant only the exact plugin permissions required ("dialog:allow-open", "fs:allow-app-data-recursive", "opener:default").
  • When exposing custom Rust commands in a workspace plugin or build.rs, register const COMMANDS: &[&str] and declare explicit allow / deny scopes rather than opening unrestricted filesystem or shell access.

Sidecar Binaries (externalBin) & Lifecycle

Remember that Tauri does not bundle Node.js:

  • If the application requires a local Node/Python/Go daemon, compile it into a standalone executable per target triple (binaries/server-aarch64-apple-darwin, binaries/server-x86_64-pc-windows-msvc.exe) and register "externalBin": ["binaries/server"] in tauri.conf.json.
  • Spawn the sidecar from Rust via app.shell().sidecar("server")?.args([...]).spawn()?, store the CommandChild handle in managed state, and hook RunEvent::ExitRequested / RunEvent::Exit in .build(...).run(|app_handle, event| ...) to terminate the sidecar cleanly when the app exits.

Included files 1

  • SKILL.md