-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[refactor] replace command macro #3256
Comments
@FabianLars Gonna ping you here, you have dealt with way more questions, maybe you wanna add something here! |
Would you have to define commands with all those One other thing that annoys me with the command macro is that rust-analyzer's intellisense seems to kinda just break in command functions |
No that is just the function signature. I'll update the post with an actual usage example 👍🏻 |
I like enforcing |
I would like to get rid of I will refactor the IPC system after v1 anyway to enable removing event handler,1 one time commands and ipc middleware. so that will be easy to accommodate |
As little magic as possible, that's important to me |
I personally like enforcing i don't rly like Also, i'm not sure if a super complicated command signature is much better than what we got or whether we will just end up trading problems for (new) problems...
So far i heard ~3 kinds of questions related to this, all mentioned by you:
|
Will you still be able to specify multiple arguments as before? async fn foo(ctx: Ctx, arg1: bool, arg2: String) -> Result<(), Error> {
println!("{} {}", arg1, arg2);
Ok(())
} Would it still work to pass a async fn foo(ctx: Ctx) -> Result<(), String> {
Err("User error message".to_string())
} And accessing state would have to work like this, correct? async fn foo(ctx: Ctx) -> Result<(), String> {
let my_struct: State<MyStruct> = ctx.app.state();
Ok(())
} |
No, not like that anyway. Taking multiple arguments would look like this: // we can use tuples here to accept more arguments than one.
// all types in the tuple need to implement Serialize of course.
// this is exactly how projects like wasmtime or deno handle typed functions
async fn foo(ctx: Ctx, args: (bool, String)) -> Result<(), Error> {
println!("{} {}", arg1, arg2);
Ok(())
} This will make commands more predictable, so when calling this from the frontend you would need to pass an array of two elements. invoke("foo", [true, "hello world"]) If users would like a different representation they can use a struct explicitly: #[derive(Serialize)]
struct Payload {
arg1: bool,
#[serde(rename = "bar")]
arg2: String
}
async fn foo(ctx: Ctx, args: Payload) -> Result<(), Error> {
println!("{} {}", arg1, arg2);
Ok(())
} invoke("foo", {
arg1: true,
bar: "hello world"
})
Yeah!
I don't particularly like that, so I'm hoping to get some feedback from y'all. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
I was sent this issue and wanted to leave some thoughts. I'm sure you have come across many of these limtations already but I wanted to document them all together for anyone coming across this issue. I have been solving a lot of similar problems to this while working on rspc as it's macroless. Concerns going macrolessAutomatic command namesIf the Tauri command system were going to go fully macroless it would be impossible to determine the name and the module path (for command namespacing) of the command function. Eg. fn demo() {}
fn cmd(func: impl Fn()) {
// TODO: How do determine the name of function ("demo") here?
} Alternative Solutions:
Vardic Arguments Into ObjectIt was shown in the code from the original issue but not fully explained. It would not be possible for vardic arguments to be converted into an object with named fields. This is because the macro is currently able to read the key of the fields from the Eg. fn demo (some_important_name: String) {}
pub trait Command {}
// This isn't valid Rust because these will conflict but this can be worked around using "generic markers"
impl<T: Fn(TArg1), TArg1: serde::Serialize> Command for T {}
impl<T: Fn(TArg1, TArg2), TArg1: serde::Serialize, TArg2: serde::Serialize> Command for T {}
fn cmd(func: impl Command) {
// TODO: How to determine the key of the argument/s ("some_important_name") here or from within the `Command` impls?
} Alternative Solutions:
Combining
|
Thanks for the additional thoughts 👍 I'll put all this together in a different issue properly labeled as an RFC tonight. Just wanted to add the approximate timeline for the macros:
Edit: I much much prefer people setting their own name for commands i.e. |
With tauri-specta you get the function exported directly anyway though, so with proper bindings this isn't an issue. Overall I'm skeptical about deprecating the command macros if it means losing named arguments, requiring |
Yeah that's true but the IPC system has to serve many more users elegantly. Even those that are not using any type generation utility.
Let me get the proper proposal done and we can feel out the DX. I'm honestly not a big fan on the named arguments as they exist right now (though I appreciate the general idea) as it tends to get really verbose, especially calling commands from Rustwasm. But again we can feel all that out. Same goes for enforcing result. |
Sure. Don't know how Rustwasm works, but the reason I like the named arguments is that it's less verbose, and imo clearer, than this: fn command(args: (String, String, bool)) {
let name = args.0;
let description = args.1;
let enabled = args.2;
} |
Oh no don't worry, on the Rust side we'll definitely keep functions with more than one argument (implemented through a macro) for up to 16 or so parameters. After that you'd have to group them using tuples or newtypes. Examples: fn valid_command() {
}
fn also_valid_command(a: u8, b: String, c: HashMap<String, String>) {
}
async fn valid_too() {}
fn invalid_bc_too_many_args(
a1: u8,
a2: u8,
a3: u8,
a4: u8,
a5: u8,
a6: u8,
a7: u8,
a8: u8,
a9: u8,
a10: u8,
a11: u8,
a12: u8,
a13: u8,
a14: u8,
a15: u8,
a16: u8,
a17: u8
) {} It's a bit sad that we can't implement the trait required for this to work for all functions, but honestly 16 parameters is plenty and real code should not approach that limit anyway (we can maybe have a nice warning for users that let's them know that these are too many parameters. just too many) |
@probablykasper It's worth noting that with this new system |
@JonasKruckenberg Is there a link to the relevant RFC somewhere, that I missed? |
Describe the problem
I noticed a recurring theme among discord questions: Issues with commands.
Here is a short list of the most common ones:
Result
#2533.__cmd__<command name> is not defined
/__cmd__my_<command name> must be defined only once
Describe the solution you'd like
While most of this can be addressed through docs, I wanna propose a in-code solution:
Usage example (using the plugin builder proposal #2959 ):
This has several advantages over the current situation:
Result
, clearly mapping to frontend promise states.FnOnce
as arguments)Incremental Adoption
The command macro can trivially use this new system under the hood, so we don't break existing code.
We could then mark it as deprecated and remove it with v3.
Alternatives considered
Additional context
This heavily inspired by denos op functions: https://github.com/denoland/deno/blob/main/core/ops_json.rs
Related issues:
#3255
#2533
#3029
Features/Use cases to consider
<module>.<command>
)The text was updated successfully, but these errors were encountered: