diff --git a/.changes/enhance-async-commands-error.md b/.changes/enhance-async-commands-error.md
new file mode 100644
index 000000000000..e7be9d49383a
--- /dev/null
+++ b/.changes/enhance-async-commands-error.md
@@ -0,0 +1,6 @@
+---
+"tauri": "patch:enhance"
+"tauri-macros": "patch:enhance"
+---
+
+Enhance the error message when using `async` commands with a reference.
diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs
index 306d964e864f..4babd0dbb325 100644
--- a/crates/tauri-macros/src/command/wrapper.rs
+++ b/crates/tauri-macros/src/command/wrapper.rs
@@ -186,9 +186,18 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
// only implemented by `Result`. That way we don't exclude renamed result types
// which we wouldn't otherwise be able to detect purely from the token stream.
// The "error message" displayed to the user is simply the trait name.
+ //
+ // TODO: remove this check once our MSRV is high enough
+ let diagnostic = if is_rustc_at_least(1, 78) {
+ quote!(#[diagnostic::on_unimplemented(message = "async commands that contain references as inputs must return a `Result`")])
+ } else {
+ quote!()
+ };
+
async_command_check = quote_spanned! {return_type.span() =>
#[allow(unreachable_code, clippy::diverging_sub_expression)]
const _: () = if false {
+ #diagnostic
trait AsyncCommandMustReturnResult {}
impl AsyncCommandMustReturnResult for ::std::result::Result {}
let _check: #return_type = unreachable!();
@@ -452,3 +461,42 @@ fn parse_arg(
}
)))
}
+
+fn is_rustc_at_least(major: u32, minor: u32) -> bool {
+ let version = rustc_version();
+ version.0 >= major && version.1 >= minor
+}
+
+fn rustc_version() -> (u32, u32) {
+ cross_command("rustc")
+ .arg("-V")
+ .output()
+ .ok()
+ .and_then(|o| {
+ let version = String::from_utf8_lossy(&o.stdout)
+ .trim()
+ .split(' ')
+ .nth(1)
+ .unwrap_or_default()
+ .split(".")
+ .take(2)
+ .flat_map(|p| p.parse::().ok())
+ .collect::>();
+ version
+ .first()
+ .and_then(|major| version.get(1).map(|minor| (*major, *minor)))
+ })
+ .unwrap_or((1, 0))
+}
+
+fn cross_command(bin: &str) -> std::process::Command {
+ #[cfg(target_os = "windows")]
+ let cmd = {
+ let mut cmd = std::process::Command::new("cmd");
+ cmd.arg("/c").arg(bin);
+ cmd
+ };
+ #[cfg(not(target_os = "windows"))]
+ let cmd = std::process::Command::new(bin);
+ cmd
+}