错误处理:让前端看得懂的失败信息
Tauri 的 Rust 命令可以直接返回 Result<T, E>,框架会自动将 Ok 的结果序列化为 JavaScript 可用的值,并在 Err 时抛出异常。为了让前端 try-catch 能拿到结构化的错误信息,你的错误类型需要实现 Serialize。实践中常用 thiserror 派生 Error 和 Display,再用 serde::Serialize 让它可序列化:
#[derive(Debug, thiserror::Error, serde::Serialize)]
enum AppError {
#[error("File not found: {0}")]
NotFound(String),
#[error("Permission denied")]
PermissionDenied,
}
#[tauri::command]
fn read_config(path: &str) -> Result<String, AppError> {
std::fs::read_to_string(path).map_err(|_| AppError::NotFound(path.into()))
}
前端调用时,就像普通 API 一样用 try-catch 处理,错误对象会包含你定义的错误信息。这让你不必手工拼接错误码,也能让 UI 做精准的提示。
异步命令:不阻塞 UI 线程
凡是涉及文件 I/O、网络请求、复杂计算的 Rust 命令,都应该声明为异步函数。Tauri 内置了 tokio 运行时,你只需在函数前加 async,并在前端使用 invoke 返回的 Promise 等待结果,整个调用期间 WebView 界面不会被冻结:
#[tauri::command]
async fn fetch_data(url: &str) -> Result<String, AppError> {
let resp = reqwest::get(url).await?;
Ok(resp.text().await?)
}
注意:异步命令内部尽量避免使用同步阻塞操作(如 std::fs::read_to_string),应改用 tokio::fs 或 spawn_blocking,否则会占用异步工作线程,导致其他命令被延迟。
状态注入:安全地共享数据库、配置等资源
像数据库连接池、应用配置、缓存这些需要在多个命令间共享的对象,可以通过 Tauri 的状态管理注入。使用 app.manage(...) 注入任意实现了 Send + Sync + 'static 的 struct,然后在命令参数中通过 State<T> 提取:
struct AppState {
db_pool: sqlx::SqlitePool,
}
#[tauri::command]
async fn get_user(id: i32, state: tauri::State<'_, AppState>) -> Result<String, AppError> {
let row = sqlx::query("SELECT name FROM users WHERE id = ?")
.bind(id)
.fetch_one(&state.db_pool)
.await?;
Ok(row.get::<String, _>("name"))
}
State 的获取是零成本抽象,不需要全局变量,也不会破坏 Rust 的所有权规则。这让你的代码更容易测试,也避免了手动管理全局锁的麻烦。
这三个特性组合在一起,基本覆盖了 Tauri 后端开发的大部分场景:你用异步保证 UI 流畅,用 Result 让前后端错误处理一致,用状态注入管理共享资源——最终得到的,是一个结构清晰、易于维护的桌面应用后端。