async commands that contain references as inputs must return a Result
主线位置:调用主线上「
#[command]包装」这一步的编译期。async 命令的包装把命令返回的 future 交给FutureTag::future或ResultFutureTag::future,再spawn到async_runtime上;前者要求 future 是'static,后者不要求。宏和这条约束的完整分析在 #[command] 与 generate_handler! 的「async 命令借用参数」一节,本页只讲怎么认出它、怎么改。
flowchart TB
fnq["async 命令"] --> refq{"参数里有引用或生命周期"}
refq -->|没有| okn["任何返回类型都行"]
refq -->|有| retq{"返回类型"}
retq -->|"Result"| okn
retq -->|"其他类型"| e1["E0277 加 E0597"]
retq -->|"没写"| e2["compile_error 加 找不到宏"]
本页实验 crate 是 pit1-async:src/bin/ 下两个编不过的 bin,src/main.rs 里是改好的命令和一个 MockRuntime 测试。M2 Max、macOS 27、rustc 1.99.0,tauri 2.12.5。
现象
一:要 State,返回 String
很自然的一种写法:命令要一个托管状态,顺手用 &str 收参数。
use tauri::State;
struct Db;
#[tauri::command]
async fn save(name: &str, _db: State<'_, Db>) -> String {
format!("saved {name}")
}
error[E0277]: async commands that contain references as inputs must return a `Result`
--> src/bin/bad.rs:6:50
|
6 | async fn save(name: &str, _db: State<'_, Db>) -> String {
| ^^^^^^ the trait `AsyncCommandMustReturnResult` is not implemented for `std::string::String`
|
help: the trait `AsyncCommandMustReturnResult` is implemented for `Result<A, B>`
--> src/bin/bad.rs:6:50
|
6 | async fn save(name: &str, _db: State<'_, Db>) -> String {
| ^^^^^^
= note: required for the cast from `&std::string::String` to `&dyn AsyncCommandMustReturnResult`
error[E0597]: `__tauri_message__` does not live long enough
--> src/bin/bad.rs:5:1
|
5 | #[tauri::command]
| ^^^^^^^^^^^^^^^^-
| | |
| | `__tauri_message__` dropped here while still borrowed
| borrowed value does not live long enough
| argument requires that `__tauri_message__` is borrowed for `'static`
...
13 | .invoke_handler(tauri::generate_handler![save])
| ------------------------------ in this macro invocation
|
note: requirement that the value outlives `'static` introduced here
--> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.12.5/src/ipc/command.rs:298:38
|
298 | F: Future<Output = T> + Send + 'static,
| ^^^^^^^
= note: this error originates in the macro `__cmd__save` which comes from the expansion of the macro `tauri::generate_handler` (in Nightly builds, run with -Z macro-backtrace for more info)
Some errors have detailed explanations: E0277, E0597.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `pit1-async` (bin "bad") due to 2 previous errors
第一条指向返回类型,第二条指向 #[tauri::command] 那一行和 generate_handler!,再指到 tauri 源码里的一行约束。两条说的是同一件事。
二:没写返回类型
#[tauri::command]
async fn log_line(line: &str) {
println!("{line}");
}
error: async commands that contain references as inputs must return a `Result`
--> src/bin/bad_unit.rs:2:19
|
2 | async fn log_line(line: &str) {
| ^^^^
error: cannot find macro `__tauri_command_name_log_line` in this scope
--> src/bin/bad_unit.rs:8:21
|
8 | .invoke_handler(tauri::generate_handler![log_line])
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `tauri::generate_handler` (in Nightly builds, run with -Z macro-backtrace for more info)
error: cannot find macro `__cmd__log_line` in this scope
--> src/bin/bad_unit.rs:8:46
|
8 | .invoke_handler(tauri::generate_handler![log_line])
| ^^^^^^^^
error: could not compile `pit1-async` (bin "bad_unit") due to 3 previous errors
这次错误指向参数,后面两条「cannot find macro」是连带的:宏只输出了 compile_error!,命令函数和它的两个辅助宏都没生成,generate_handler! 找不到它们。只看最后两条会以为是 generate_handler! 写错了。
出处
宏的 wrapper 检查 async 命令的参数:类型是引用,或者是带生命周期参数的路径类型(State<'_, T>、tauri::ipc::Request<'_> 这一类),就要求返回 Result。有返回类型时,检查是一个只为 Result 实现的 trait,报 E0277;没有返回类型时直接 compile_error!。
真正的约束在运行时那一侧。返回普通值的 async 命令走 FutureTag::future,要求 F: Future<Output = T> + Send + 'static;save(name, db) 返回的 future 借用了包装里的局部变量 __tauri_message__,不是 'static,所以 E0597。返回 Result 的走 ResultFutureTag::future,没有 'static,借用在外层 async 块里闭合。宏的检查只是把 E0597 提前翻译成一句人话。为什么两条路径的约束不一样,见 #[command] 与 generate_handler!。
同步命令没有这个限制:它在主线程上当场执行完(分发),借用活不过这次调用也没关系。
修法
两种改法,选一种:
// 修法一:借用参数保留,返回 Result
#[tauri::command]
async fn save(name: &str, _db: State<'_, Db>) -> Result<String, String> {
Ok(format!("saved {name}"))
}
// 修法二:参数改成拥有所有权的类型,返回什么都行
#[tauri::command]
async fn log_line(line: String) {
println!("log_line: {line}");
}
要 State 的 async 命令只能用修法一,State<'_, T> 本身带生命周期。用 MockRuntime 调一次(cargo test --bin pit1-async -- --nocapture,只编这一个 bin,跳过那两个编不过的):
running 1 test
save {"name":"rie"} => Ok(Json("\"saved rie\""))
log_line: hi
log_line {"line":"hi"} => Ok(Json("null"))
test tests::fixed_commands ... ok
错误类型 E 只要实现了 Into<InvokeError> 就行,String 最省事;想让 JS 拿到结构化的错误,见 参数、返回值与 InvokeResolver 的错误一节。修法一里 Result 的 Err 一次都不返回也没关系,这个约束只看类型。
怎么确认没中招
- 报错里出现
AsyncCommandMustReturnResult、__tauri_message__ does not live long enough或cannot find macro __cmd__<命令名>,都先看这个命令是不是 async、参数里有没有&或'_。 - 写 async 命令时默认返回
Result<T, String>(或者自己的错误类型),以后加State参数不用再改签名。 - 想在 async 命令里保留借用、又不想改返回类型,没有第三种办法:
Cow<str>这类省略生命周期的写法会先被 rustc 以 E0726 拒绝,写成Cow<'_, str>又会被宏认出来。
小结
- async 命令的参数里有引用或带生命周期的类型(包括
State<'_, T>)时,返回类型必须是Result。 - 有返回类型时是 E0277 加 E0597 两条;没有返回类型时是一条
compile_error!加两条「cannot find macro」。 - 原因在
FutureTag::future的'static约束;返回Result走的ResultFutureTag::future没有这个约束。 - 改成返回
Result,或者参数改成String这类拥有所有权的类型。